자바스크립트의 Call Stack, Microtask Queue, Macrotask Queue 완벽 정리
“왜 Promise.then()이 setTimeout()보다 먼저 실행될까?”
“Call Stack, Queue, Event Loop은 서로 어떤 관계일까?”
이 글에서는 자바스크립트의 이벤트 루프(Event Loop) 를 구성하는 핵심 요소
Call Stack, Microtask Queue, Macrotask Queue의 구조와 실행 순서를
실제 코드 예시로 완벽히 정리해봅니다.
🧩 1️⃣ 자바스크립트의 실행 모델
자바스크립트는 단일 스레드(Single Thread) 언어입니다.
즉, 한 번에 한 줄의 코드만 실행할 수 있습니다.
하지만 브라우저나 Node.js 환경은
비동기 코드를 지원하기 위해 이벤트 루프(Event Loop) 구조를 사용합니다.
이 이벤트 루프는 세 가지 주요 구성요소로 작동합니다 👇
| 구성 요소 | 설명 |
| Call Stack | 현재 실행 중인 함수들의 “실행 컨텍스트”를 쌓아두는 스택 구조 |
| Macrotask Queue | 다음 이벤트 루프 사이클에서 실행할 “큰 단위 작업”들의 대기열 |
| Microtask Queue | 현재 매크로태스크가 끝난 직후 즉시 실행해야 할 “짧은 작업”들의 대기열 |
⚙️ 2️⃣ 각 구성요소의 역할 요약
🔹 Call Stack
지금 실행 중인 함수들의 호출 흐름을 추적하는 실행 컨텍스트 스택
함수 호출 시 → push
함수 실행 완료 시 → pop
예시:
function a() {
b();
}
function b() {
c();
}
function c() {
console.log("Hello");
}
a();
실행 중 Call Stack 상태:
[c()]
[b()]
[a()]
🔹 Macrotask Queue
“큰 단위의 비동기 작업”을 저장하는 큐.
이벤트 루프 한 사이클당 매크로태스크 하나만 꺼내 실행합니다.
대표 예시:
<script>전체 실행setTimeout,setIntervalI/O,UI EventpostMessage,MessageChannelsetImmediate(Node.js)
🔹 Microtask Queue
매크로태스크가 끝난 직후 즉시 실행해야 하는 “짧은 비동기 작업”들의 큐.
모든 마이크로태스크가 비워질 때까지 연속 실행됩니다.
대표 예시:
Promise.then,Promise.catch,Promise.finallyqueueMicrotaskMutationObserverprocess.nextTick(Node.js)
🔄 3️⃣ 이벤트 루프의 동작 순서
1. 하나의 매크로태스크 실행
2. 그 안에서 생성된 모든 마이크로태스크 실행
3. 렌더링 단계(브라우저)
4. 다음 매크로태스크 실행
다시 말해,
“Call Stack이 비면 → Microtask Queue를 모두 처리 → 다음 Macrotask로 이동”
이 순서가 이벤트 루프의 핵심 원리입니다.
🧩 4️⃣ 예제 코드로 이해하기
console.log("1");
setTimeout(() => console.log("2 (macrotask)"), 0);
Promise.resolve()
.then(() => console.log("3 (microtask)"))
.then(() => console.log("4 (microtask)"));
console.log("5");
실행 결과:
1
5
3 (microtask)
4 (microtask)
2 (macrotask)
실행 순서 분석
| 단계 | Call Stack | Microtask Queue | Macrotask Queue | 출력 |
| A | console.log("1") 실행 | - | - | 1 |
| B | setTimeout 실행 | - | 콜백 등록 | |
| C | Promise.then 실행 | 콜백 등록 | 콜백 등록 | |
| D | console.log("5") 실행 | then 콜백 대기 | setTimeout 대기 | 5 |
| E | 스택이 비어 Microtask 실행 | 모두 실행 (3,4) | setTimeout 대기 | 3, 4 |
| F | Microtask 큐 비워짐 → Macrotask 실행 | - | setTimeout 콜백 실행 | 2 |
⚙️ 5️⃣ Call Stack과 큐의 관계
Call Stack은 “지금 실행 중인 함수들”을 추적하고,Macrotask/Microtask Queue는 “다음에 실행할 함수들”을 보관합니다.
이벤트 루프는 Call Stack이 비워지는 순간을 감지해
→ Microtask Queue부터 모두 실행
→ 이후 Macrotask Queue에서 다음 함수를 꺼냅니다.
🧠 6️⃣ setTimeout은 Stack에 들어갈까?
setTimeout(() => console.log("A"), 0);
setTimeout호출 자체는 Stack에 잠깐 올라갑니다.
(함수 호출이므로 push → 내부 타이머 등록 → pop)타이머가 만료되면 콜백은 Macrotask Queue에 들어감.
그 후 이벤트 루프가 큐에서 꺼내 Stack에 올려 실행합니다.
즉,
setTimeout함수 본체는 Stack에 들어왔다가 바로 빠지고,
그 콜백은 나중에 Stack에 올라가 실행된다.
🧩 7️⃣ 전체 구조 시각화
┌────────────────────┐
│ Call Stack │ ← 현재 실행 중인 코드
└────────┬───────────┘
│
▼
┌────────────────────────────┐
│ Microtask Queue │ ← Promise.then, queueMicrotask
└────────────────────────────┘
│
▼
┌────────────────────────────┐
│ Macrotask Queue │ ← setTimeout, I/O, UI Event
└────────────────────────────┘
│
▼
Event Loop 순환
💡 8️⃣ 정리 요약
| 구분 | 실행 시점 | 예시 | 우선순위 |
| Call Stack | 현재 실행 중 | 함수 호출, return | 최고 |
| Microtask Queue | 매크로태스크 직후 | Promise.then, queueMicrotask | 높음 |
| Macrotask Queue | 한 루프마다 1개씩 | setTimeout, I/O, <script> | 낮음 |
🧩 9️⃣ 핵심 문장으로 기억하기
“Call Stack은 지금 실행 중인 코드,
Microtask Queue는 지금 끝난 매크로태스크 직후 바로 실행할 코드,
Macrotask Queue는 다음 이벤트 루프 사이클에 실행할 코드다.”
🧭 10️⃣ 결론
<script>실행 자체도 하나의 매크로태스크이다.Promise.then()은 항상setTimeout()보다 먼저 실행된다.Call Stack이 비면 → Microtask 큐를 모두 비우고 → 다음 Macrotask 실행
이벤트 루프는 이 세 요소의 협력으로 비동기를 구현한다.
✍️ 참고 요약 코드
setTimeout(() => console.log("Macrotask"), 0);
queueMicrotask(() => console.log("Microtask"));
console.log("Sync");
// 실행 결과:
// Sync
// Microtask
// Macrotask
✅ 마무리
이제 “이벤트 루프가 어떻게 비동기 코드를 처리하는지”를
단순 암기가 아닌 정확한 실행 순서로 이해할 수 있습니다.
✨ 기억하세요:
Call Stack은 ‘현재 실행 중’,
Microtask Queue는 ‘지금 바로 실행해야 할’,
Macrotask Queue는 ‘다음 턴에 실행할’ 작업의 집합입니다.
🧾 작성 참고
이 글은 ChatGPT의 도움을 받아 내용을 정리하였습니다.