자바스크립트는 싱글 스레드 기반으로 동작하기 때문에, 한 번에 하나의 작업만 처리할 수 있습니다.
따라서 콜 스택(Call Stack), 이벤트 루프(Event Loop), Web API, 큐(Queue)를 통해 동기/비동기 작업을 처리하게 됩니다.
✅ 예제 코드
console.log(1);
setTimeout(() => {
console.log(2)
}, 100);
console.log(3);
1. 동기 작업 진행
console.log(1)
이 콜 스택에 올라가 실행됩니다.setTimeout(...)
실행setTimeout
함수 자체는 콜 스택에서 실행되지만,- 내부 콜백 함수
() => console.log(2)
는 Web API 영역으로 넘겨집니다. - Web API가 100ms 타이머를 실행하고, 끝나면 Task Queue에 콜백을 넣습니다.
console.log(3)
이 콜 스택에 올라가 실행됩니다.
2. 비동기 콜백 대기 및 실행
- 이 시점에서 Call Stack은 비어 있습니다.
- 100ms 후 Web API가 콜백을 Task Queue에 넣습니다.
3. 이벤트 루프가 큐를 확인
- 이벤트 루프는 Call Stack이 비었는지 확인하고,
- 비어 있으면 Task Queue에서 콜백을 꺼내 Call Stack에 넣어 실행합니다.
🎯 최종 출력 순서
1
3
2
🧱 각 영역 정리
개념 | 역할 |
---|---|
Call Stack | 현재 실행 중인 동기 코드 저장소 |
Web APIs | setTimeout, fetch, DOM 이벤트 등을 비동기로 처리하는 브라우저 제공 영역 |
Task Queue | 비동기 콜백이 완료되면 저장되는 큐 (Macrotask Queue) |
Event Loop | Call Stack이 비면 큐에서 작업을 꺼내 Stack으로 보냄 |
중요: Event Loop는 Call Stack이 완전히 비어야 Task Queue에서 작업을 꺼낼 수 있습니다.
⛔ 동기 작업이 길어지면?
예를 들어, setTimeout(..., 3000)
으로 예약된 함수가 있어도 동기 코드가 3초 이상 실행 중이라면, 정확히 3초 뒤에 실행되지 않습니다.
상황 | 이유 |
---|---|
CPU 오래 점유하는 동기 코드 | 타이머 콜백이 뒤로 밀림 |
UI 작업이 멈추는 경우 | 사용자 경험 저하 |
정확한 시간 제어가 필요한 경우 | requestAnimationFrame 또는 Web Worker 사용 고려 |
📌 Microtask vs Macrotask
큐 종류 | 대표 예시 | 우선 순위 |
---|---|---|
Microtask Queue | Promise.then , queueMicrotask , async/await |
높음 |
Macrotask Queue | setTimeout , setInterval , DOM 이벤트 등 |
낮음 |
작동 순서:
- Call Stack이 비면
- Microtask Queue 먼저 모두 처리
- 그 다음 Macrotask를 하나 처리
- 다시 1번부터 반복
console.log(1);
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log(2);
출력 순서:
1
2
promise
setTimeout
📎 2초가 걸리는 Promise는 어떻게 동작할까?
1. 동기적으로 2초를 블로킹하는 경우
console.log(1);
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
const start = Date.now();
while (Date.now() - start < 2000) {} // 2초 동안 블로킹
console.log('promise');
});
console.log(2);
출력 순서:
1
2
promise
setTimeout
설명:
- Microtask(Promise)는 Macrotask(setTimeout)보다 우선
- 하지만 Microtask에서 블로킹 코드가 있으면 Macrotask도 기다려야 함
2. 비동기 2초 후 실행되는 경우
console.log(1);
setTimeout(() => {
console.log('setTimeout');
}, 0);
function fakeFetch(delay = 1000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve("🧪 fake data");
}, delay);
});
}
async function run() {
const result = await fakeFetch(2000);
console.log("promise:", result);
}
run();
console.log(2);
출력 순서:
1
2
setTimeout
promise: 🧪 fake data
왜 이렇게 동작할까?
run()
호출 →await fakeFetch(2000)
→ Promise 반환 → 중단setTimeout(..., 0)
→ Macrotask로 등록console.log(2)
실행- 0ms Macrotask 먼저 실행 →
setTimeout
출력 - 2초 뒤 Promise resolve →
await
이후 코드가 Microtask로 등록됨 - Call Stack이 비면 → Microtask 실행 →
promise
출력
📘 의문 정리
1. 왜 await
을 만나면 pause되는가?
await
는Promise.then(...)
과 유사하게 동작- Promise가 resolve될 때까지 함수 실행을 중단(pause)함
- 그 이후 코드는 Microtask Queue에 등록되어 실행됨
2. 왜 await
아래 코드는 Microtask Queue에 등록되는가?
await
는Promise.then(...)
과 같은 방식으로 동작.then()
안의 콜백은 Microtask로 등록되기 때문에await
아래 코드도 Microtask로 처리됨
'Web Development > JAVASCRIPT' 카테고리의 다른 글
상태관리 [5편]: Redux, Zustand, Tanstack Query — 언제, 무엇을 써야 할까? (0) | 2025.03.22 |
---|---|
상태관리 [4편]: Tanstack-Query, 왜 프론트엔드에서 상태를 나눠 관리해야 할까? (0) | 2025.03.22 |
상태관리 [3편] - Zustand의 유연함이 대규모 프로젝트에서 어려운 이유 (0) | 2025.03.22 |
상태관리 [2편] - Redux vs Zustand 비교 – 왜 생겼고 언제 쓰는가? (0) | 2025.03.22 |
상태관리 [1편] - Flux 패턴(아키텍처) (0) | 2025.03.21 |