아래의 코드는 Promise 가 이벤트 루프에서 어떻게 처리되는지 확인해보기 위한 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const prom1 = () => {
return new Promise((resolve, reject) => {
console.log('prom1');
resolve();
});
}
const prom2 = () => {
return new Promise((resolve, reject) => {
console.log('prom2');
setTimeout(() => {
console.log('promise -> setTimeout');
resolve();
}, 0);
});
}
const prom3 = () => {
return new Promise((resolve, reject) => {
console.log('prom3');
reject();
});
}
console.log('before promise');
setTimeout(() => console.log('setTimeout before promise'), 0);
prom1().then(() => console.log('prom1 -> then'));
prom2().then(() => console.log('prom2 -> setTimeout -> then'));
prom3().catch(() => console.log('prom3 -> catch'));
console.log('after promise');
위 코드를 실행하면 다음과 같은 결과가 나온다.
1
2
3
4
5
6
7
8
9
10
before promise
prom1
prom2
prom3
after promise
prom1 -> then
prom3 -> catch
setTimeout before promise
prom2 -> setTimeout
prom2 -> setTimeout -> then
실행 결과를 보면, Promise 의 executor 함수는 호출되는 시점에서 바로 실행이 되어 “before promise”, “prom1”, “prom2”, “prom3” , “after promise” 가 순서대로 출력된다. 하지만, executor 함수에서 호출한 resolve 함수와 reject 함수에 대한 콜백은 바로 처리되지 않고 콜백 큐로 이동되어 “after promise” 까지 출력이 완료된 이후에 처리되는 것을 볼 수 있다.
그리고 한가지 더 눈여겨 볼 점은 prom1 보다 먼저 호출된 setTimeout 함수의 콜백과 prom2의 콜백 실행 순서다.
Micro Task Queue
prom1 보다 먼저 호출된 setTimeout 함수의 콜백은 prom3 의 콜백까지 완료 된 후 실행되었고, prom2 의 콜백은 첫 번째 setTimeout 콜백이 완료 된 이후에 처리되었다.
그 이유는, ES6 를 지원하는 자바스크립트 런타임 환경에서는 콜백 큐에 Micro Task Queue 가 추가되었기 때문인데, Promise 의 콜백은 이 Micro Task Queue 로 추가된 후 가장 우선적으로 처리된다. 그렇기 때문에 첫번째 setTimeout 함수의 콜백이 prom3 의 콜백까지 실행이 완료된 이후에 실행되었던 것이다.
prom2 의 경우 executor 함수에서 setTimeout 함수의 콜백으로 resolve 함수를 호출하기 때문에 일단 setTimeout 함수의 콜백이 기본 Task Queue 로 이동 된다. 그 다음 Task Queue 에서 첫번째 setTimeout 함수의 콜백이 처리된 이후 콜백이 실행되고 그때서야 resolve 함수의 콜백이 micro task queue 로 이동되어 호출되기 때문에 가장 마지막에 실행되는 것이다.
만약, 여러개의 then 을 체이닝하여 호출하면 실행 순서는 어떻게 될지 궁금하여 실험해보았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const prom1 = () =>
new Promise((resolve, reject) => {
console.log('prom1');
resolve();
});
const prom2 = () =>
new Promise((resolve, reject) => {
console.log('prom2');
resolve();
});
const prom3 = () =>
new Promise((resolve, reject) => {
console.log('prom3');
resolve();
});
prom1()
.then(() => console.log('prom1 -> then'))
.then(() => console.log('prom1 -> then -> then'))
.then(() => console.log('prom1 -> then -> then -> then'));
prom2()
.then(() => console.log('prom2 -> then'))
.then(() => console.log('prom2 -> then -> then'));
prom3().then(() => console.log('prom3 -> then'));
실행결과는 다음과 같으며, 각 Promise 의 첫번째 then 콜백이 모두 실행되고 그 다음 체이닝 된 두 번째 then 콜백이 모두 실행되고 그 다음 세번째 체이닝 된 콜백이 실행되는 것을 확인 할 수 있었다. 왜 그럴까 생각해보니 첫번째 then 이 실행된 후 그 다음 then 이 이어서 실행되는 것이 아니라 콜백 큐로 이동되기 때문임을 유추해 낼 수 있었다.
1
2
3
4
5
6
7
8
9
prom1
prom2
prom3
prom1 -> then
prom2 -> then
prom3 -> then
prom1 -> then -> then
prom2 -> then -> then
prom1 -> then -> then -> then