Node.js 기본 구조
graph TB
subgraph STACK[Call Stack]
S1[현재 실행 중인 함수]
end
subgraph LOOP[Event Loop]
EL[Call Stack 비었나?<br/>→ Queue에서 가져오기]
end
subgraph QUEUES[Queues]
MQ[Microtask Queue<br/>Promise, await 완료]
TQ[Task Queue<br/>setTimeout, I/O, Cron]
end
STACK --> |비어있을 때만| LOOP
LOOP --> MQ
MQ --> |비었으면| TQ
TQ --> STACK
style STACK fill:#ff6b6b
style MQ fill:#4ecdc4
style TQ fill:#45b7d1핵심 규칙: Call Stack이 비어야만 다음 작업 실행
오해 1: “setTimeout(0)은 즉시 실행된다”
코드
console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");실행 흐름
sequenceDiagram
participant CS as Call Stack
participant TQ as Task Queue
participant OUT as 출력
Note over CS: console.log('1')
CS->>OUT: "1"
Note over CS: setTimeout 등록
CS->>TQ: 콜백 등록 (0ms여도!)
Note over CS: console.log('3')
CS->>OUT: "3"
Note over CS: Call Stack 비었음!
TQ->>CS: 콜백 가져오기
Note over CS: 콜백 실행
CS->>OUT: "2"출력: 1 → 3 → 2
setTimeout(0)은 “다음 Event Loop에 실행"이라는 뜻!
오해 2: “await은 프로그램을 멈춘다”
코드
async function fetchData() {
console.log("fetch 시작");
await sleep(3000);
console.log("fetch 끝");
}
fetchData();
console.log("다음 코드");실행 흐름
sequenceDiagram
participant CS as Call Stack
participant MQ as Microtask Queue
participant OUT as 출력
Note over CS: fetchData() 실행
CS->>OUT: "fetch 시작"
Note over CS: await 만남!
CS-->>MQ: Promise 등록, 제어권 반환
Note over CS: fetchData 일시 중단
Note over CS: 다음 코드 실행 가능!
CS->>OUT: "다음 코드"
Note over CS: Call Stack 비었음
Note over MQ: 3초 후 Promise 완료
MQ->>CS: fetchData 재개
CS->>OUT: "fetch 끝"출력: fetch 시작 → 다음 코드 → (3초 후) fetch 끝
오해 3: “Promise와 setTimeout은 같은 우선순위”
코드
console.log("1");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("2");실행 흐름
sequenceDiagram
participant CS as Call Stack
participant MQ as Microtask Queue
participant TQ as Task Queue
participant OUT as 출력
Note over CS: 동기 코드 실행
CS->>OUT: "1"
CS->>TQ: setTimeout 콜백 등록
CS->>MQ: Promise.then 등록
CS->>OUT: "2"
Note over CS: Call Stack 비었음!
Note over MQ: Microtask 먼저!
MQ->>CS: Promise 콜백
CS->>OUT: "promise"
Note over TQ: 그 다음 Task
TQ->>CS: setTimeout 콜백
CS->>OUT: "timeout"출력: 1 → 2 → promise → timeout
오해 4: “await은 동기처럼 동작한다”
비교
graph LR
subgraph SYNC[진짜 동기 대기]
A1[while로 CPU 점유] --> A2[전체 프로그램 블록]
A2 --> A3[아무것도 못함]
end
subgraph AWAIT[await 대기]
B1[Promise 등록] --> B2[제어권 반환]
B2 --> B3[다른 코드 실행 가능!]
end
style A3 fill:#ff6b6b
style B3 fill:#4ecdc4// ❌ 진짜 동기 (전체 블록)
function syncWait(ms) {
const end = Date.now() + ms;
while (Date.now() < end) {} // CPU 점유!
}
// ✅ await (해당 함수만 중단)
await new Promise((r) => setTimeout(r, 3000));오해 5: “for 안의 await은 병렬”
순차 실행 (기본)
gantt
title for-await 순차 실행
dateFormat X
axisFormat %s초
section 처리
item 1 :0, 1
item 2 :1, 2
item 3 :2, 3for (const item of items) {
await processItem(item); // 하나씩 순차!
}
// 총 3초병렬 실행 (Promise.all)
gantt
title Promise.all 병렬 실행
dateFormat X
axisFormat %s초
section 처리
item 1 :0, 1
item 2 :0, 1
item 3 :0, 1await Promise.all(items.map((item) => processItem(item)));
// 총 1초!Event Loop 흐름도 (핵심!)
flowchart TD
START([시작]) --> CS{Call Stack에<br/>동기 코드 있음?}
CS -->|예| EXEC[실행!<br/>끝날 때까지 독점]
EXEC --> POP[Call Stack에서 제거]
POP --> CS
CS -->|아니오| MQ{Microtask Queue에<br/>작업 있음?}
MQ -->|예| MICRO[모든 Microtask 실행<br/>Promise.then, await 완료]
MICRO --> MQ
MQ -->|아니오| TQ{Task Queue에<br/>작업 있음?}
TQ -->|예| TASK[하나만 Call Stack에<br/>올려서 실행]
TASK --> CS
TQ -->|아니오| IDLE[대기 idle]
IDLE --> CS
style EXEC fill:#ff6b6b
style MICRO fill:#4ecdc4
style TASK fill:#45b7d1우선순위
graph LR
A[1️⃣ Call Stack<br/>동기 코드] --> B[2️⃣ Microtask Queue<br/>Promise, await]
B --> C[3️⃣ Task Queue<br/>setTimeout, I/O]
style A fill:#ff6b6b,color:#fff
style B fill:#4ecdc4,color:#fff
style C fill:#45b7d1,color:#fff핵심 요약
| 오해 | 실제 |
|---|---|
| setTimeout(0)은 즉시 실행 | ❌ Task Queue 경유 후 실행 |
| await은 프로그램을 멈춤 | ❌ 해당 함수만 중단, 다른 코드 실행 가능 |
| Promise와 setTimeout 우선순위 같음 | ❌ Microtask(Promise)가 먼저 |
| await은 동기 코드 | ❌ 비동기, Event Loop에 제어권 반환 |
| for-await은 병렬 | ❌ 순차 실행, 병렬은 Promise.all |
한 줄 정리
await = “나(이 함수)는 여기서 기다릴게, Event Loop는 그 동안 다른 일 해!”