편집을 취소할 수 있습니다. 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 게시해주세요.
최신판 | 당신의 편집 | ||
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]이었을 정도. | ||
== 예 == | == 예 == | ||
< | <source lang="javascript"> | ||
step1(function (value1) { | step1(function (value1) { | ||
step2(function (value2) { | step2(function (value2) { | ||
21번째 줄: | 19번째 줄: | ||
}); | }); | ||
}); | }); | ||
</ | </source> | ||
step1에서 어떤 처리 (주로 입출력) 이후 그 결과를 받아와, 인자로 전달된 익명 함수의 매개변수로 넘겨준다. 이후 step2에서 또 어떤 처리를 하고, 다음 익명 함수가 실행된다. 이를 반복하다보면 코드가 위에서 아래가 아니라 기묘한 피라미드 모양으로 기술되게 된다. 여기서 에러 처리가 포함되면 더욱 가관이다. | step1에서 어떤 처리 (주로 입출력) 이후 그 결과를 받아와, 인자로 전달된 익명 함수의 매개변수로 넘겨준다. 이후 step2에서 또 어떤 처리를 하고, 다음 익명 함수가 실행된다. 이를 반복하다보면 코드가 위에서 아래가 아니라 기묘한 피라미드 모양으로 기술되게 된다. 여기서 에러 처리가 포함되면 더욱 가관이다. | ||
< | <source lang="javascript"> | ||
step1(function (err, value1) { | step1(function (err, value1) { | ||
if (err) { | if (err) { | ||
45번째 줄: | 43번째 줄: | ||
}); | }); | ||
}); | }); | ||
</ | </source> | ||
== 해결 방안 == | == 해결 방안 == | ||
=== 동기 함수를 사용한다 === | === 동기 함수를 사용한다 === | ||
클라이언트 사이드의 ajax나, Node.js 환경의 fs.readFile과 같은 비동기 함수의 경우 동기 버전을 지원한다. Node.js에서 file.txt라는 파일을 읽어와 콘솔에 출력하는 코드는 다음과 같다. | 클라이언트 사이드의 ajax나, Node.js 환경의 fs.readFile과 같은 비동기 함수의 경우 동기 버전을 지원한다. Node.js에서 file.txt라는 파일을 읽어와 콘솔에 출력하는 코드는 다음과 같다. | ||
< | <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); | ||
}); | }); | ||
</ | </source> | ||
이는 비동기 함수로, 리턴값은 없고 대신 콜백 함수에 결과를 전달한다. 동기 버전의 함수를 이용하면 다음과 같다. | 이는 비동기 함수로, 리턴값은 없고 대신 콜백 함수에 결과를 전달한다. 동기 버전의 함수를 이용하면 다음과 같다. | ||
< | <source lang="javascript"> | ||
var result = fs.readFileSync('file.txt', 'utf8'); | var result = fs.readFileSync('file.txt', 'utf8'); | ||
console.log(result); | console.log(result); | ||
</ | </source> | ||
위에서 아래로, 아주 깔끔하고 전통적인 코드가 된다. 그러나 이 방법은 문제가 있는데, 이렇게 동기화된 입출력을 사용하면 그동안 다른 작업을 하나도 처리할 수 없게 된다는 점이다. 파일을 여는데 시간이 오래 걸린다면 특히나 문제가 크다. 브라우저 환경의 경우 JS가 실행되는 환경이 브라우저의 다른 작업들과 리소스를 공유하기 때문에 (JS의 주목적인 DOM 처리를 위해서 어쩔 수 없다.) 페이지 로딩이 잠시 멈춰 버리는 등, 사용자 경험에 아주 좋지 못한 영향을 미치게 된다. 또한 Node.js를 이용해 서버를 굴린다면, 한 사람의 요청이 끝날 때까지 다른 사람의 요청은 받을 수가 없다. 서버가 영 제 구실을 못하게 되는 것. 그래서 되도록이면 이 방법은 피하는게 좋다. 다만 서버의 초기 실행 단계와 같이 여러 작업을 병행할 필요가 딱히 없는 상황에서는 사용해도 무방하다. 또 다른 문제라면 많은 비동기 입출력 모듈 (mysql 등)이 동기 입출력을 아예 지원하지 않는 경우도 많다는 것. | 위에서 아래로, 아주 깔끔하고 전통적인 코드가 된다. 그러나 이 방법은 문제가 있는데, 이렇게 동기화된 입출력을 사용하면 그동안 다른 작업을 하나도 처리할 수 없게 된다는 점이다. 파일을 여는데 시간이 오래 걸린다면 특히나 문제가 크다. 브라우저 환경의 경우 JS가 실행되는 환경이 브라우저의 다른 작업들과 리소스를 공유하기 때문에 (JS의 주목적인 DOM 처리를 위해서 어쩔 수 없다.) 페이지 로딩이 잠시 멈춰 버리는 등, 사용자 경험에 아주 좋지 못한 영향을 미치게 된다. 또한 Node.js를 이용해 서버를 굴린다면, 한 사람의 요청이 끝날 때까지 다른 사람의 요청은 받을 수가 없다. 서버가 영 제 구실을 못하게 되는 것. 그래서 되도록이면 이 방법은 피하는게 좋다. 다만 서버의 초기 실행 단계와 같이 여러 작업을 병행할 필요가 딱히 없는 상황에서는 사용해도 무방하다. 또 다른 문제라면 많은 비동기 입출력 모듈 (mysql 등)이 동기 입출력을 아예 지원하지 않는 경우도 많다는 것. | ||
=== 콜백 함수를 분리한다 === | === 콜백 함수를 분리한다 === | ||
콜백 지옥이 발생하는 이유는 익명 함수를 연달아서 사용하기 때문이다. 익명 함수의 사용을 포기하고, 함수를 나눠 버리면 깔끔하다. 맨 위의 예를 다음과 같이 고칠 수 있다. | 콜백 지옥이 발생하는 이유는 익명 함수를 연달아서 사용하기 때문이다. 익명 함수의 사용을 포기하고, 함수를 나눠 버리면 깔끔하다. 맨 위의 예를 다음과 같이 고칠 수 있다. | ||
< | <source lang="javascript"> | ||
step1(afterStep1); | step1(afterStep1); | ||
function afterStep1(value1) { | function afterStep1(value1) { | ||
73번째 줄: | 71번째 줄: | ||
} | } | ||
// 생략 | // 생략 | ||
</ | </source> | ||
위와 같이 하면, 콜백 함수를 계속 사용하더라도 들여쓰기가 깊어지지 않아 가독성이 향상된다. 단점은 전 단계에서 정의된 변수 등을 다음 함수에서 사용할 수 없다는 점. 완전히 바깥쪽에 변수를 선언해 사용해야 한다. 또한 이름 짓기도 영 힘들어진다. | 위와 같이 하면, 콜백 함수를 계속 사용하더라도 들여쓰기가 깊어지지 않아 가독성이 향상된다. 단점은 전 단계에서 정의된 변수 등을 다음 함수에서 사용할 수 없다는 점. 완전히 바깥쪽에 변수를 선언해 사용해야 한다. 또한 이름 짓기도 영 힘들어진다. | ||
=== Async 모듈 사용 === | |||
=== Promise 패턴 도입 === | === Promise 패턴 도입 === | ||
Promise는 콜백 문제를 해결하기 위해 제안된 패턴 중 하나로 여러 라이브러리를 통해 지원되어 오다가, | Promise는 콜백 문제를 해결하기 위해 제안된 패턴 중 하나로 여러 라이브러리를 통해 지원되어 오다가, ECMAScript 2015 표준으로 지정되었다. | ||
< | <source lang="javascript"> | ||
somethingAsync(value1) | somethingAsync(value1) | ||
.then((result) => { | .then((result) => { | ||
88번째 줄: | 88번째 줄: | ||
// 실패시 수행할 작업 | // 실패시 수행할 작업 | ||
}); | }); | ||
</ | </source> | ||
then이나 catch 내에서는 어떤 값을 리턴하거나, 다른 Promise를 리턴할 수도 있다. 또한 then과 catch는 계속 붙여서 쓸 수 있다. | then이나 catch 내에서는 어떤 값을 리턴하거나, 다른 Promise를 리턴할 수도 있다. 또한 then과 catch는 계속 붙여서 쓸 수 있다. | ||
< | <source lang="javascript"> | ||
somethingAsync(value1) | somethingAsync(value1) | ||
.then((result) => { | .then((result) => { | ||
113번째 줄: | 113번째 줄: | ||
// catch를 사용하는 대신에 then의 두번째 인자를 사용할 수도 있다. | // catch를 사용하는 대신에 then의 두번째 인자를 사용할 수도 있다. | ||
}); | }); | ||
</ | </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> | |||
< | <source lang="javascript"> | ||
function f() { | function f() { | ||
somethingAsync(value1) | |||
.then((result) => { | .then((result) => { | ||
// 성공시 수행할 작업 | // 성공시 수행할 작업 | ||
134번째 줄: | 134번째 줄: | ||
}); | }); | ||
} | } | ||
</ | </source> | ||
이는 다음 코드와 같다. | 이는 다음 코드와 같다. | ||
< | <source lang="javascript"> | ||
async function f() { | async function f() { | ||
try { | try { | ||
146번째 줄: | 146번째 줄: | ||
} | } | ||
} | } | ||
</ | </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를 사용함으로써, 동기 함수와 완전히 같은 방식으로 비동기 함수를 사용하면서, 비동기 함수의 장점을 잃지 않을 수 있다 | async - await를 사용함으로써, 동기 함수와 완전히 같은 방식으로 비동기 함수를 사용하면서, 비동기 함수의 장점을 잃지 않을 수 있다. | ||
{{각주}} | {{틀:각주}} | ||
[[분류: | [[분류:프로그래밍]] | ||