나중에 하기: setTimeout과 Promise: 나중에 올 값에서 setTimeout(..., 0)의 콜백과 Promise.then(...)의 콜백은 둘 다 '지금 도는 코드가 끝난 뒤'에 실행된다고 배웠습니다. 그렇다면 둘이 한 코드에 함께 있을 때는 누가 먼저 실행될까요?
할 일 줄보다 빠른 Promise 줄
자바스크립트에는 나중에 실행할 함수를 세워 두는 대기 줄이 두 줄 있습니다.
- Promise 줄: 이행된
Promise의.then·.catch·.finally콜백이 서는 줄입니다. - 할 일 줄:
setTimeout·setInterval타이머 콜백이 서는 줄입니다.
지금 도는 코드가 파일 끝까지 다 끝나면, 자바스크립트는 늘 Promise 줄을 먼저 끝까지 다 비운 뒤에야 할 일 줄에서 타이머 함수를 꺼냅니다(규칙 8).
console.log('a');
setTimeout(() => {
console.log('타이머');
}, 0);
Promise.resolve().then(() => {
console.log('then');
});
console.log('b');
a
b
then
타이머코드에서는 setTimeout이 2번 줄로 더 위에 적혀 있지만, 출력은 then이 타이머보다 먼저 찍힙니다. 두 대기 줄이 어떻게 채워지고 비워지는지 그림으로 확인해 보세요.
- 지금 도는 코드: 1번 줄
'a'와 8번 줄'b'가 가장 먼저 실행됩니다. - Promise 줄 (1순위): 지금 도는 코드가 끝나자마자 Promise 줄에 있던
'then'이 먼저 꺼내져 실행됩니다. - 할 일 줄 (2순위): Promise 줄이 완전히 비워진 뒤에야 할 일 줄에 있던
0밀리초'타이머'가 실행됩니다.
then 사슬이 이어질 때의 차례
Promise 줄의 함수가 실행되면서 또 다른 .then을 Promise 줄에 넣으면 어떻게 될까요? Promise 줄이 완전히 비워질 때까지 할 일 줄은 기다리므로, 이어 붙은 두 번째 .then까지도 0밀리초 타이머보다 먼저 실행됩니다.
setTimeout(() => {
console.log('타이머');
}, 0);
Promise.resolve()
.then(() => {
console.log('then 1');
})
.then(() => {
console.log('then 2');
});
console.log('끝');
끝
then 1
then 2
타이머지금 도는 코드의 '끝'이 찍힌 뒤, Promise 줄의 'then 1'과 이어진 'then 2'가 모두 끝나고 나서야 할 일 줄의 '타이머'가 찍힙니다.
자주 틀리는 곳
1. 코드에서 먼저 등록한 쪽이 먼저 돈다고 생각함
setTimeout(..., 0)을 첫 줄에 적고 Promise.resolve().then(...)을 맨 아랫줄에 적어도, 두 함수가 들어가는 대기 줄 자체가 다릅니다. 등록한 순서와 상관없이 이행된 Promise 줄이 할 일 줄보다 늘 먼저입니다.
2. 아직 이행되지 않은 Promise의 then도 무조건 먼저라고 생각함
Promise 줄에 들어가는 시점은 그 Promise가 이행(resolve)된 때입니다. 만약 어떤 Promise가 100밀리초 타이머 안에서 resolve된다면, 0밀리초 타이머가 먼저 돌고 100밀리초 뒤에야 그 Promise가 이행되어 .then이 돕니다.
setTimeout(() => {
console.log('0ms 타이머');
}, 0);
new Promise((resolve) => {
setTimeout(resolve, 50);
}).then(() => {
console.log('50ms 뒤 then');
});
0ms 타이머
50ms 뒤 then요약
- 자바스크립트의 실행 차례(규칙 8): 지금 도는 코드 → Promise 줄(
.then) → 할 일 줄(setTimeout). - 이미 이행된
Promise.resolve().then(...)은 코드 아래쪽에 적혀 있어도setTimeout(..., 0)보다 먼저 실행된다. - Promise 줄의
.then이 이어 붙인 다음.then도 Promise 줄이 다 비워질 때까지 타이머보다 먼저 실행된다.
연습 문제
문제 1
다음 코드를 실행했을 때 출력되는 네 줄을 마지막 줄까지 순서대로 쓰세요.
setTimeout(() => {
console.log(1);
}, 0);
Promise.resolve().then(() => {
console.log(2);
});
console.log(3);
Promise.resolve().then(() => {
console.log(4);
});
힌트
지금 도는 코드(3)가 먼저 찍히고, 그다음 Promise 줄에 순서대로 들어간 2와 4가 찍힌 뒤, 마지막으로 할 일 줄의 1이 찍힙니다.
정답
3
2
4
1- 7번 줄의
3은 지금 도는 코드라 가장 먼저 찍힙니다. - 4~6번 줄과 8~10번 줄의 두
.then콜백이 Promise 줄에 차례대로 들어가2,4를 찍습니다. - Promise 줄이 비워진 뒤 할 일 줄의
1이 마지막에 찍힙니다.
문제 2
다음 코드는 '순서: A' → '순서: B' → '순서: C' 차례로 찍히게 하려는데, '순서: B'를 setTimeout(..., 0)으로 적어서 맨 마지막에 찍혔습니다. 2~4번 줄을 Promise.resolve().then으로 고쳐 A, B, C 차례로 찍히게 만드세요.
console.log('순서: A');
setTimeout(() => {
console.log('순서: B');
}, 0);
setTimeout(() => {
console.log('순서: C');
}, 10);
힌트
2번 줄의 setTimeout(() => {를 Promise.resolve().then(() => {로, 4번 줄의 }, 0);를 });로 바꾸면 '순서: B'가 '순서: C'(10ms 타이머)보다 먼저 찍힙니다.
정답
console.log('순서: A');
Promise.resolve()
.then(() => {
console.log('순서: B');
});
setTimeout(() => {
console.log('순서: C');
}, 10);순서: A
순서: B
순서: C문제 3
지금 도는 코드에서 '시작'을 찍고, Promise 줄에서 '중간'을 찍고, 0밀리초 타이머로 할 일 줄에서 '끝'을 찍는 코드를 작성하세요. 단, 코드 줄의 순서는 타이머('끝') → Promise('중간') → 지금 코드('시작') 차례로 적으세요.
시작
중간
끝
// 1. 0ms 타이머로 '끝' 예약
// 2. Promise 줄에 '중간' 예약
// 3. console.log('시작')
힌트
코드에 적힌 순서가 거꾸로여도 지금 도는 코드 → Promise 줄 → 할 일 줄 차례로 실행되어 시작, 중간, 끝이 찍힙니다.
정답
setTimeout(() => {
console.log('끝');
}, 0);
Promise.resolve()
.then(() => {
console.log('중간');
});
console.log('시작');시작
중간
끝