콜백 지옥 편집하기


편집하면 당신의 IP 주소가 공개적으로 기록됩니다. 계정을 만들고 로그인하면 편집 시 사용자 이름만 보이며, 위키 이용에 여러 가지 편의가 주어집니다.

편집을 취소할 수 있습니다. 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 게시해주세요.

최신판 당신의 편집
3번째 줄: 3번째 줄:
== 개요 ==
== 개요 ==
콜백 지옥은 [[JavaScript]]를 이용한 비동기 프로그래밍시 발생하는 문제로서, 함수의 매개 변수로 넘겨지는 [[콜백 함수]]가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다. 이게 얼마나 혐오스러웠는지, [[express (프레임워크)]]등을 제작한 node.js의 거물 프로그래머 [https://github.com/tj TJ]가 js를 버리고 go로 가며 남긴 말이 [https://medium.com/@tjholowaychuk/farewell-node-js-4ba9e7f3e52b#.aaahwwn9a callbacks suck]이었을 정도.
콜백 지옥은 [[JavaScript]]를 이용한 비동기 프로그래밍시 발생하는 문제로서, 함수의 매개 변수로 넘겨지는 [[콜백 함수]]가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다. 이게 얼마나 혐오스러웠는지, [[express (프레임워크)]]등을 제작한 node.js의 거물 프로그래머 [https://github.com/tj TJ]가 js를 버리고 go로 가며 남긴 말이 [https://medium.com/@tjholowaychuk/farewell-node-js-4ba9e7f3e52b#.aaahwwn9a callbacks suck]이었을 정도.
[[2019년]] 기준으로는 [[Promise]]가 자바스크립트 생태계에서 표준처럼 사용되며 이를 이용한 async/await 패턴 역시 많이 사용하고 있어 콜백 지옥을 겪는 일이 거의 없어졌다고 봐도 무방하다.


== 예 ==
== 예 ==
<syntaxhighlight lang="javascript">
<source lang="javascript">
step1(function (value1) {
step1(function (value1) {
     step2(function (value2) {
     step2(function (value2) {
21번째 줄: 19번째 줄:
     });
     });
});
});
</syntaxhighlight>
</source>
step1에서 어떤 처리 (주로 입출력) 이후 그 결과를 받아와, 인자로 전달된 익명 함수의 매개변수로 넘겨준다. 이후 step2에서 또 어떤 처리를 하고, 다음 익명 함수가 실행된다. 이를 반복하다보면 코드가 위에서 아래가 아니라 기묘한 피라미드 모양으로 기술되게 된다. 여기서 에러 처리가 포함되면 더욱 가관이다.
step1에서 어떤 처리 (주로 입출력) 이후 그 결과를 받아와, 인자로 전달된 익명 함수의 매개변수로 넘겨준다. 이후 step2에서 또 어떤 처리를 하고, 다음 익명 함수가 실행된다. 이를 반복하다보면 코드가 위에서 아래가 아니라 기묘한 피라미드 모양으로 기술되게 된다. 여기서 에러 처리가 포함되면 더욱 가관이다.
<syntaxhighlight lang="javascript">
<source lang="javascript">
step1(function (err, value1) {
step1(function (err, value1) {
     if (err) {
     if (err) {
45번째 줄: 43번째 줄:
     });
     });
});
});
</syntaxhighlight>
</source>


== 해결 방안 ==
== 해결 방안 ==
=== 동기 함수를 사용한다 ===
=== 동기 함수를 사용한다 ===
클라이언트 사이드의 ajax나, Node.js 환경의 fs.readFile과 같은 비동기 함수의 경우 동기 버전을 지원한다. Node.js에서 file.txt라는 파일을 읽어와 콘솔에 출력하는 코드는 다음과 같다.
클라이언트 사이드의 ajax나, Node.js 환경의 fs.readFile과 같은 비동기 함수의 경우 동기 버전을 지원한다. Node.js에서 file.txt라는 파일을 읽어와 콘솔에 출력하는 코드는 다음과 같다.
<syntaxhighlight lang="javascript">
<source lang="javascript">
fs.readFile('file.txt', 'utf8', function (err, result) {
fs.readFile('file.txt', 'utf8', function (err, result) {
     console.log(result);
     console.log(result);
});
});
</syntaxhighlight>
</source>
이는 비동기 함수로, 리턴값은 없고 대신 콜백 함수에 결과를 전달한다. 동기 버전의 함수를 이용하면 다음과 같다.
이는 비동기 함수로, 리턴값은 없고 대신 콜백 함수에 결과를 전달한다. 동기 버전의 함수를 이용하면 다음과 같다.
<syntaxhighlight lang="javascript">
<source lang="javascript">
var result = fs.readFileSync('file.txt', 'utf8');
var result = fs.readFileSync('file.txt', 'utf8');
console.log(result);
console.log(result);
</syntaxhighlight>
</source>
위에서 아래로, 아주 깔끔하고 전통적인 코드가 된다. 그러나 이 방법은 문제가 있는데, 이렇게 동기화된 입출력을 사용하면 그동안 다른 작업을 하나도 처리할 수 없게 된다는 점이다. 파일을 여는데 시간이 오래 걸린다면 특히나 문제가 크다. 브라우저 환경의 경우 JS가 실행되는 환경이 브라우저의 다른 작업들과 리소스를 공유하기 때문에 (JS의 주목적인 DOM 처리를 위해서 어쩔 수 없다.) 페이지 로딩이 잠시 멈춰 버리는 등, 사용자 경험에 아주 좋지 못한 영향을 미치게 된다. 또한 Node.js를 이용해 서버를 굴린다면, 한 사람의 요청이 끝날 때까지 다른 사람의 요청은 받을 수가 없다. 서버가 영 제 구실을 못하게 되는 것. 그래서 되도록이면 이 방법은 피하는게 좋다. 다만 서버의 초기 실행 단계와 같이 여러 작업을 병행할 필요가 딱히 없는 상황에서는 사용해도 무방하다. 또 다른 문제라면 많은 비동기 입출력 모듈 (mysql 등)이 동기 입출력을 아예 지원하지 않는 경우도 많다는 것.
위에서 아래로, 아주 깔끔하고 전통적인 코드가 된다. 그러나 이 방법은 문제가 있는데, 이렇게 동기화된 입출력을 사용하면 그동안 다른 작업을 하나도 처리할 수 없게 된다는 점이다. 파일을 여는데 시간이 오래 걸린다면 특히나 문제가 크다. 브라우저 환경의 경우 JS가 실행되는 환경이 브라우저의 다른 작업들과 리소스를 공유하기 때문에 (JS의 주목적인 DOM 처리를 위해서 어쩔 수 없다.) 페이지 로딩이 잠시 멈춰 버리는 등, 사용자 경험에 아주 좋지 못한 영향을 미치게 된다. 또한 Node.js를 이용해 서버를 굴린다면, 한 사람의 요청이 끝날 때까지 다른 사람의 요청은 받을 수가 없다. 서버가 영 제 구실을 못하게 되는 것. 그래서 되도록이면 이 방법은 피하는게 좋다. 다만 서버의 초기 실행 단계와 같이 여러 작업을 병행할 필요가 딱히 없는 상황에서는 사용해도 무방하다. 또 다른 문제라면 많은 비동기 입출력 모듈 (mysql 등)이 동기 입출력을 아예 지원하지 않는 경우도 많다는 것.


=== 콜백 함수를 분리한다 ===
=== 콜백 함수를 분리한다 ===
콜백 지옥이 발생하는 이유는 익명 함수를 연달아서 사용하기 때문이다. 익명 함수의 사용을 포기하고, 함수를 나눠 버리면 깔끔하다. 맨 위의 예를 다음과 같이 고칠 수 있다.
콜백 지옥이 발생하는 이유는 익명 함수를 연달아서 사용하기 때문이다. 익명 함수의 사용을 포기하고, 함수를 나눠 버리면 깔끔하다. 맨 위의 예를 다음과 같이 고칠 수 있다.
<syntaxhighlight lang="javascript">
<source lang="javascript">
step1(afterStep1);
step1(afterStep1);
function afterStep1(value1) {
function afterStep1(value1) {
73번째 줄: 71번째 줄:
}
}
// 생략
// 생략
</syntaxhighlight>
</source>


위와 같이 하면, 콜백 함수를 계속 사용하더라도 들여쓰기가 깊어지지 않아 가독성이 향상된다. 단점은 전 단계에서 정의된 변수 등을 다음 함수에서 사용할 수 없다는 점. 완전히 바깥쪽에 변수를 선언해 사용해야 한다. 또한 이름 짓기도 영 힘들어진다.
위와 같이 하면, 콜백 함수를 계속 사용하더라도 들여쓰기가 깊어지지 않아 가독성이 향상된다. 단점은 전 단계에서 정의된 변수 등을 다음 함수에서 사용할 수 없다는 점. 완전히 바깥쪽에 변수를 선언해 사용해야 한다. 또한 이름 짓기도 영 힘들어진다.
=== Async 모듈 사용 ===


=== Promise 패턴 도입 ===
=== Promise 패턴 도입 ===
Promise는 콜백 문제를 해결하기 위해 제안된 패턴 중 하나로 여러 라이브러리를 통해 지원되어 오다가, [[ECMAScript 2015]] 표준으로 지정되었다.
Promise는 콜백 문제를 해결하기 위해 제안된 패턴 중 하나로 여러 라이브러리를 통해 지원되어 오다가, ECMAScript 2015 표준으로 지정되었다.


<syntaxhighlight lang="javascript">
<source lang="javascript">
somethingAsync(value1)
somethingAsync(value1)
     .then((result) => {
     .then((result) => {
88번째 줄: 88번째 줄:
         // 실패시 수행할 작업
         // 실패시 수행할 작업
     });
     });
</syntaxhighlight>
</source>


then이나 catch 내에서는 어떤 값을 리턴하거나, 다른 Promise를 리턴할 수도 있다. 또한 then과 catch는 계속 붙여서 쓸 수 있다.
then이나 catch 내에서는 어떤 값을 리턴하거나, 다른 Promise를 리턴할 수도 있다. 또한 then과 catch는 계속 붙여서 쓸 수 있다.


<syntaxhighlight lang="javascript">
<source lang="javascript">
somethingAsync(value1)
somethingAsync(value1)
     .then((result) => {
     .then((result) => {
113번째 줄: 113번째 줄:
         // catch를 사용하는 대신에 then의 두번째 인자를 사용할 수도 있다.
         // catch를 사용하는 대신에 then의 두번째 인자를 사용할 수도 있다.
     });
     });
</syntaxhighlight>
</source>


Promise가 return되는 경우, 비동기 작업이 끝나 resolve나 reject가 호출될 때까지는 정지해 있다가, resolve나 reject가 실행되면 then이나 catch를 호출한다.
Promise가 return되는 경우, 비동기 작업이 끝나 resolve나 reject가 호출될 때까지는 정지해 있다가, resolve나 reject가 실행되면 then이나 catch를 호출한다.
122번째 줄: 122번째 줄:


=== Async Function (async - await) ===
=== Async Function (async - await) ===
[[ECMAScript 2017]] 표준. [[파이어폭스]], [[구글 크롬]], [[마이크로소프트 엣지]], [[사파리 (웹 브라우저)|사파리]], [[삼성 브라우저]]를 비롯한 대부부의 최신 브라우저에서 사용이 가능하고, node.js는 7.6.0 이후 버전에서 별도 설정 없이 사용할 수 있다. IE 등의 옛날 브라우저에서 이용하기 위해서는 Babel 등의 자바스크립트 트랜스파일러를 사용하면 된다. <ref> http://kangax.github.io/compat-table/es2016plus/ </ref>
ECMAScript 2017 표준으로 제안된 기능. 일부 최신 브라우저에는 구현되어 있으며, node.js 7.6.0 이후 버전 역시 별도 설정 없이 사용할 수 있게 되었다. IE 등의 옛날 브라우저에서 이용하기 위해서는 Babel 등의 자바스크립트 트랜스파일러를 사용하면 된다. <ref> http://kangax.github.io/compat-table/es2016plus/ </ref>


<syntaxhighlight lang="javascript">
<source lang="javascript">
function f() {
function f() {
     return somethingAsync(value1)
     somethingAsync(value1)
         .then((result) => {
         .then((result) => {
             // 성공시 수행할 작업
             // 성공시 수행할 작업
134번째 줄: 134번째 줄:
         });
         });
}
}
</syntaxhighlight>
</source>
이는 다음 코드와 같다.
이는 다음 코드와 같다.


<syntaxhighlight lang="javascript">
<source lang="javascript">
async function f() {
async function f() {
     try {
     try {
146번째 줄: 146번째 줄:
     }
     }
}
}
</syntaxhighlight>
</source>


await는 promise가 완료될 때까지 함수를 정지시키고, 완료되었을 경우 resolve되는 변수를 함수의 리턴값인 것처럼 사용할 수 있게 한다. 만약 promise가 reject된 경우, try ~ catch 구문을 이용해 잡을 수 있다. await를 사용하기 위해서는 그 코드가 실행되는 함수가 async function으로 선언되어야 하며, async function에서 리턴되는 값은 promise에서 resolve하는 것으로 취급한다.
await는 promise가 완료될 때까지 함수를 정지시키고, 완료되었을 경우 resolve되는 변수를 함수의 리턴값인 것처럼 사용할 수 있게 한다. 만약 promise가 reject된 경우, try ~ catch 구문을 이용해 잡을 수 있다. await를 사용하기 위해서는 그 코드가 실행되는 함수가 async function으로 선언되어야 하며, async function에서 리턴되는 값은 promise에서 resolve하는 것으로 취급한다.


async - await를 사용함으로써, 동기 함수와 완전히 같은 방식으로 비동기 함수를 사용하면서, 비동기 함수의 장점을 잃지 않을 수 있다. 단점이라면 아직까지 콜백 방식이나 Promise를 직접 이용하는 것보다 퍼포먼스가 낮다는 점. 그러나 async function은 자바스크립트 생태계에 빠르게 자리잡았고, 오히려 기존 콜백 방식의 코드를 보기 어려워질 지경이 되었다.
async - await를 사용함으로써, 동기 함수와 완전히 같은 방식으로 비동기 함수를 사용하면서, 비동기 함수의 장점을 잃지 않을 수 있다.


{{각주}}
{{틀:각주}}


[[분류:컴퓨터 프로그래밍]]
[[분류:프로그래밍]]
[[분류:JavaScript]]
리브레 위키에서의 모든 기여는 크리에이티브 커먼즈 저작자표시-동일조건변경허락 3.0 라이선스로 배포됩니다(자세한 내용에 대해서는 리브레 위키:저작권 문서를 읽어주세요). 만약 여기에 동의하지 않는다면 문서를 저장하지 말아 주세요.
글이 직접 작성되었거나 호환되는 라이선스인지 확인해주세요. 리그베다 위키, 나무위키, 오리위키, 구스위키, 디시위키 및 CCL 미적용 사이트 등에서 글을 가져오실 때는 본인이 문서의 유일한 기여자여야 하고, 만약 본인이 문서의 유일한 기여자라는 증거가 없다면 그 문서는 불시에 삭제될 수 있습니다.
취소 편집 도움말 (새 창에서 열림)

| () [] [[]] {{}} {{{}}} · <!-- --> · [[분류:]] · [[파일:]] · [[미디어:]] · #넘겨주기 [[]] · {{ㅊ|}} · <onlyinclude></onlyinclude> · <includeonly></includeonly> · <noinclude></noinclude> · <br /> · <ref></ref> · {{각주}} · {|class="wikitable" · |- · rowspan=""| · colspan=""| · |}

이 문서에서 사용한 틀: