JS Async Functionality 2 - Promise
이 시리즈는 자바스크립트에서 비동기를 다룰 때 마주치는 개념들을 다룬다. 이번 글에서는 Promise를 다룬다.
JS Async Functionality 1 - Intro
Promise:
{ pending, fulfilled, rejected }
상태를 가지는 객체로, executor
함수를 인자로 받는다.
executor 함수(
(resolve, reject ) => {}
)의 역할:- 비동기 함수를 호출하고
- 그 비동기 함수의 콜백에서 resolve를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13const fetch = url => new Promise((resolve, reject) => {
// 이 함수가 executor 함수이다. (주석 설명 참조)
// 1. example Async API provided by Node.js
http.get(options, result => {
let data;
result.on('data', chunk => data += chunk);
// 2. Calls either [ resolve, reject ] from async callback.
result.on('end', () => resolve(data));
result.on('error', err => reject(err));
});
});executor 함수는 기존의 비동기 처리 방식을 그대로 옮겨온 것으로 이해하기 어렵지 않다.
다만 Promise Chaining이라는 개념으로 Callback Hell을 1차원으로 들여쓰기 단계를 낮출 수 있다.
이렇게 들여쓰기 단계를 줄이는 것은 중요한데 가독성에 의한 논리 오류가 빈번하게 발생했기 때문이다.
또한 Javascript 특성상 CPS 패턴으로 작성된 비동기 처리 함수에서, 이후에 호출되는 함수는 이전 함수의 클로저 참조도 할 수 있다.
- 부주의하게 클로저 영역의 변수들을 사용하는 경우 메모리 사용량 면에서 좋을 게 없었다.
- (ex) 전체 비동기 절차가 끝날 때 까지 호출 함수의 지역 변수들이 해제되지 못하는 등.
Promise Chaining:
Promise는 타입이자 객체이다. Promise(
- Prototype에 등록된 함수로, then은 Promise를 반환하고(그래서 Chaining이 가능하고) catch는 reject된 Promise에 한 해 수행되는 조건문으로
then(undefined, onRejected)
와 동등하다. finally는 JS의 try-catch-finally의 finally와 동등하다.
then
then
= (onFulfilled, onRejected) => Promise
즉 then의 두 번째 인자는 catch 절인 셈이다. 웬만하면 가독성을 위해 따로 catch 절을 사용한다.
onFulfilled = value => Promise (여기서 value는 Promise가 resolve한 값이다. 보통 비동기 함수의 결괏값.)
onRejected = value => {} (여기서 value는 Promise가 reject한 값이다. 보통 Error 객체.)
1
2
3
4
5
6
7p.then(onFulfilled, onRejected);
p.then(function(value) {
// 비동기가 별 탈 없이 진행된 경우.
}, function(reason) {
// 비동기 함수 수행 중 오류가 난 경우.
});then에서는 값을 그냥 반환하는 경우 Promise.resolve로 감싼 것과 같다. 즉 Promise가 반환되는 것인데, 그렇기 때문에 Promise Chaining이 가능한 것이다.
catch
catch
= onRejected => Promise (!)
catch 메소드는
try-catch
의catch
와 같은 역할이다. 즉, catch가 성공했느냐, 실패했느냐에 따라 다시 then이 실행될 수도 있고 다른 catch가 실행될 수도 있고 앱이 멈출 수도 있다.Case 1: catch 절에서 resolved Promise를 반환하는 경우: 이후의 then 수행
catch 절에서 따로 throw를 하거나,
Promise.reject()
를 호출하지 않는 경우 Promise는 resolved 상태로 변하여 then을 수행한 것과 동등하게 된다.Case 2: catch 절에서 rejected Promise를 반환하는 경우: 이후의 catch 수행
JS의 try-catch에서 catch는 여러 개가 존재할 수 없는 것에 비해 Promise가 rejected 상태이면 catch절은 계속해서 호출된다. 보통 여러 개의 catch 절은 특정 오류만 잡고 싶을 때 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
// 아래처럼 pin-point로 catch 절을 사용하는 것이 가능하다.
// 기존 JS에서 try-catch를 여러 번 순차적으로 사용한 것과 동등하다.
// 1.Promise를 생성
p1.then(function(value) { // 1. throw를 호출해 catch 절로 이동
throw new Error('oh, no!');
}).catch(function(e) { // 2. reject 혹은 throw를 하지 않으므로 then 수행
console.error(e.message);
}).then(function(){ // 3. 이 then이 수행되게 됨.
console.log('after a catch the chain is restored');
}).catch(function () { // 4. 만약 [2], [3]에서 throw를 하는 경우 여기로 오게 됨.
console.log('Not fired due to the catch');
});
finally
(설명 생략)
알기 어려운 Promise의 특징
1. then, catch는 비동기로 실행된다.
아무리 Promise.resolve(); 로 resolve가 동기로 수행되더라도 then, catch는 비동기로 queue 된다.
1
2
3
4
5
6
7Promise.resolve(null).then(v => console.log('Async: ' + v));
console.log('Sync!');
// 결과:
// Sync!
// Async: null
// WHY? then이 async로 microtask queue에 들어갔기 때문.
// then이
2: then, catch는 비동기이지만 한꺼번에 수행된다.
만약 then, catch가 setTimeout과 같은 일반적인 비동기였다면 Task Queue에서 처리된다. Task Queue는 한 작업만 처리하고 나머지 작업은 다음 순서로 넘긴다.
1
2
3
4
5
6function loop() {
setTimeout(loop, 0);
}
loop();
// 무한 루프에 걸리지 않는다.
// Microtask가 아니므로 이벤트 루프에서 한 작업씩만(!!!) 처리한다.
그러나 Promise, then, catch는 Microtask Queue에서 수행되는데, 이는 Event Loop 내의 Event Loop으로 생각하면 된다.
굳이 이렇게 하는 이유는
- 다른 Javascript 수행이 되지 않음을 보장
- 화면이 변경되지 않음을 보장 하기 위해서이다.
Microtask가 호출한 microtask 역시 이어서 수행되며 microtask queue가 빌 때까지 이 단계는 끝나지 않는다.
1
2
3
4
5
6
7function loop() {
// then은 microtask에 queue 된다.
Promise.resolve().then(loop);
}
loop();
// microtask는 현재 cycle에서 microtask가 비워질 때까지 수행을 멈추지 않는다.
// 즉 무한 루프를 비동기 코드로 발생시킬 수 있는 셈이다.출처: 이벤트 루프와 매크로, 마이크로 태스크, Jake Archibald: Inside Loop - JSConf.Asia | Youtube
Promise가 연속적으로 수행되어 문제가 발생하는 예제를 생각하려고 했으나 대부분의 비동기는 microtask를 사용하지 않기에 큰 문제는 없을 것 같다. 따라서 이 본문의 내용을 몰라도 거의 문제는 없을 것 같다.
출처
Promise catch (재밌는 점은 catch 문서는 한국어 번역이 없다는 점이다.)
TODO
처음에 React를 통해 ES6를 배우면서 Promise를 접했을 때보다 문서 개수나 번역이 훨씬 좋아졌다는 걸 느꼈다. 앞으로의 JS 표준을 다루는 MDN Wiki 문서가 있으면 나도 기여해야겠다