
브라우저/Node.js 런타임에서 비동기 코드의 실행 순서를 정확히 이해하는 것은 디버깅과 성능 최적화의 출발점입니다. 특히 마이크로태스크 큐는 렌더링 타이밍과 상태 일관성에 직접적인 영향을 주므로, 실무에서 정확한 모델을 갖추는 것이 중요합니다.
setTimeout, setInterval, setImmediate(Node) 등이 들어가는 큐.Promise.then/catch/finally, queueMicrotask, MutationObserver 등이 들어가는 큐.요약하면, “마이크로태스크는 같은 틱에서 모두 비워진 뒤에야 다음 매크로태스크로 넘어간다”가 핵심입니다.
아래 코드는 test.js와 동일합니다.
setTimeout(() => {
console.log('A');
}, 0);
console.log('B');
Promise.resolve().then(() => {
console.log('C');
});
queueMicrotask(() => {
console.log('D');
});
console.log('E');
B
E
C
D
A
1) setTimeout(..., 0) 등록 → Web APIs로 타이머 위임 → 만료 후 Task Queue에 콜백(A) 대기
2) 동기 코드 실행: console.log('B')
3) Promise.resolve().then(...) → Microtask Queue에 콜백(C) 등록
4) queueMicrotask(...) → Microtask Queue에 콜백(D) 등록
5) 동기 코드 실행: console.log('E')
6) 콜스택이 비면 Event Loop가 Microtask Queue를 “모두” 비움 → C → D
7) 다음 틱에서 Task Queue의 A 실행 → console.log('A')
정리: 동기(B, E) → 마이크로태스크(C, D) → 매크로태스크(A)
마이크로태스크 내부 순서는 “등록된 순서”입니다. 위 코드에서는 Promise.then이 먼저, queueMicrotask가 나중에 등록되어 C → D 순으로 실행됩니다.
| 항목 | setTimeout | Promise.then | queueMicrotask |
|---|---|---|---|
| 큐 유형 | Task(매크로태스크) | Microtask | Microtask |
| 실행 시점 | 다음 틱 이후 | 현재 틱의 콜스택 종료 직후 | 현재 틱의 콜스택 종료 직후 |
| 순서 보장 | 동일 지연이면 상대적 | FIFO(등록 순) | FIFO(등록 순) |
| 렌더링 영향 | 보통 렌더 후 실행 | 렌더 전 마이크로태스크가 모두 비워짐 | 렌더 전 마이크로태스크가 모두 비워짐 |
| 에러 전파 | 타이머 콜백에서 throw 시 비동기 에러 | 잡히지 않으면 전역 Unhandled Rejection | 잡히지 않으면 전역 에러 |
| 취소 | clearTimeout | 불가(체인 취소는 별도 로직) | 불가 |
| 대표 용도 | 지연 실행, 렌더 이후 작업 | 비동기 후크/체인 | 매우 짧은 후처리, 동 틱 내 정합 보장 |
queueMicrotask가 가장 명확합니다.Promise.then vs queueMicrotaskPromise가 편리합니다. 아주 경량 후처리는 queueMicrotask가 적합합니다.process.nextTick이 마이크로태스크보다 우선 실행되어 기아를 유발하기 쉽습니다. 범용적으로는 queueMicrotask/setImmediate 사용을 권장합니다.await Promise.resolve()로 한 틱을 양보하는 패턴을 사용할 수 있습니다.1) 타이머 내부의 마이크로태스크는 “다음 매크로태스크 안”에서 먼저 비워집니다.
setTimeout(() => {
console.log('A1');
queueMicrotask(() => console.log('A2')); // 같은 틱에서 A2가 A1 바로 뒤에 실행
}, 0);
console.log('B1');
예상: B1 → (다음 틱) A1 → A2
2) 등록 순서가 곧 마이크로태스크 실행 순서입니다.
queueMicrotask(() => console.log('M1'));
Promise.resolve().then(() => console.log('M2'));
queueMicrotask(() => console.log('M3'));
예상: M1 → M2 → M3
queueMicrotask로, 체인/에러 전파가 필요하면 Promise로.setTimeout(0)이 아니라, 의도에 맞는 API(requestAnimationFrame, requestIdleCallback)를 고려하세요.process.nextTick 남용을 지양하고 queueMicrotask/setImmediate를 선택하세요.핵심 공식: 동기(B/E) → 마이크로태스크(C/D 전체 소진) → 매크로태스크(A). 이 규칙으로 대부분의 실행 순서를 안정적으로 예측할 수 있습니다.