← 개인 공부 · Frontend
STUDY NOTE · 개념 정리

자바스크립트 이벤트 루프 개념 정리: 콜 스택, 태스크 큐, 마이크로태스크

Frontend
자바스크립트 엔진은 싱글 스레드라 한 번에 하나의 코드만 실행한다. 이벤트 루프는 콜 스택이 비었을 때 대기 중인 작업을 정해진 순서로 꺼내 실행하는 장치로, 싱글 스레드로도 비동기 처리를 가능하게 한다.

1. 구성 요소

구성 요소역할
콜 스택 (Call Stack)현재 실행 중인 함수들이 쌓이는 곳. 한 번에 하나만 실행
힙 (Heap)객체가 저장되는 메모리 영역
Web API (브라우저)setTimeout, fetch, DOM 이벤트 등을 엔진 밖에서 처리하고, 끝나면 콜백을 큐에 넣음
태스크 큐 (매크로태스크)setTimeout, setInterval, 이벤트 핸들러, I/O 콜백 대기열
마이크로태스크 큐Promise 콜백(then/catch/finally), await 이후 코드, queueMicrotask, MutationObserver 대기열
이벤트 루프콜 스택이 비면 큐에서 작업을 꺼내 콜 스택으로 보냄

타이머 시간을 세거나 네트워크 응답을 기다리는 일은 엔진이 아니라 브라우저(Node에서는 libuv)가 한다. 엔진은 결과 콜백만 받아 실행한다.

2. 이벤트 루프 한 바퀴

  1. 태스크 큐에서 하나를 꺼내 실행 (처음엔 스크립트 전체가 하나의 태스크)
  2. 콜 스택이 비면 마이크로태스크 큐를 빌 때까지 전부 실행 (실행 중 새로 추가된 것도 포함)
  3. 렌더링이 필요하면: requestAnimationFrame 콜백 → 스타일 계산 → 레이아웃 → 페인트
  4. 1로 돌아감

3. 태스크 vs 마이크로태스크

구분태스크 (매크로태스크)마이크로태스크
예setTimeout, setInterval, 이벤트 핸들러, MessageChannelPromise.then, await 이후, queueMicrotask
한 바퀴에 실행하나큐가 빌 때까지 전부
우선순위낮음높음 (현재 태스크 직후)
사이에 렌더링태스크와 태스크 사이에 가능마이크로태스크 사이에는 불가

4. 실행 순서 예제

console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
queueMicrotask(() => console.log("4"));
console.log("5");

출력은 1 5 3 4 2다.

시점실행이유
현재 태스크1, 5동기 코드는 즉시 실행
마이크로태스크3, 4현재 태스크가 끝나면 전부 실행 (들어간 순서대로)
다음 태스크2setTimeout 콜백은 태스크 큐

5. async / await

await 앞까지는 동기로 실행되고, await 이후의 코드는 마이크로태스크로 예약된다.

async function f() {
console.log("A");
await null;
console.log("B");
}
f();
console.log("C");
// 출력: A C B

6. requestAnimationFrame과 타이머

구분setTimeout(fn, 16)requestAnimationFrame(fn)
실행 시점최소 16ms 이후 아무 태스크 차례다음 화면을 그리기 직전
화면 주기와 동기화안 됨 (끊김 발생)됨
백그라운드 탭최소 간격이 늘어남멈춤
용도지연 실행애니메이션, 화면 변화 측정

setTimeout(fn, 0)도 0ms 뒤가 아니라 "현재 태스크와 마이크로태스크가 모두 끝난 뒤 다음 태스크 차례"다. 중첩 타이머가 5단계를 넘으면 브라우저가 최소 4ms로 늘린다.

7. 브라우저와 Node.js의 차이

구분브라우저Node.js
비동기 처리 주체Web APIlibuv
태스크 단계태스크 큐 하나로 보면 됨timers → pending → poll → check → close 단계로 나뉨
추가 큐-process.nextTick 큐 (Promise 마이크로태스크보다 먼저)
고유 APIrequestAnimationFramesetImmediate (check 단계)

8. 실제로 어떻게 적용되나

  1. 화면이 멈추는 이유 이해: 렌더링은 태스크 사이에서만 일어난다. 한 태스크가 1초 걸리면 그동안 화면과 클릭이 모두 멈춘다. 무거운 작업은 여러 태스크로 쪼개거나(setTimeout, scheduler.yield()) Web Worker로 옮긴다.
  2. 마이크로태스크로 쪼개면 안 되는 이유: await Promise.resolve()로 쪼개면 마이크로태스크라 큐가 빌 때까지 이어서 실행되므로 렌더링 기회가 생기지 않는다. 쪼갤 때는 태스크를 써야 한다.
  3. 애니메이션과 측정: 화면 변화에 맞춰야 하는 애니메이션은 requestAnimationFrame을 쓴다. 반응속도 테스트처럼 "화면이 바뀐 시점"을 재는 경우에도 상태를 바꾼 시각이 아니라 rAF 시각을 기준으로 잡아야 실제로 보인 시점에 가깝다.
  4. 정확한 시간 측정: setTimeout은 "최소 이 시간 뒤"라는 뜻이라 오차가 있다. 경과 시간이 필요하면 performance.now()로 직접 계산한다.

9. 면접 질문으로 정리

  1. Q. 자바스크립트가 싱글 스레드인데 비동기가 가능한 이유는? 타이머·네트워크 같은 작업은 브라우저(Web API)나 libuv가 처리하고, 완료되면 콜백을 큐에 넣는다. 이벤트 루프가 콜 스택이 빌 때마다 큐의 콜백을 꺼내 실행하기 때문이다.
  2. Q. 태스크와 마이크로태스크의 차이는? 태스크는 setTimeout·이벤트 등으로 한 바퀴에 하나씩 실행되고, 마이크로태스크는 Promise 콜백 등으로 현재 태스크가 끝난 직후 큐가 빌 때까지 전부 실행된다. 그래서 Promise 콜백이 setTimeout(0)보다 먼저 실행된다.
  3. Q. setTimeout(fn, 0)은 바로 실행되나? 아니다. 현재 실행 중인 코드와 모든 마이크로태스크가 끝난 뒤 태스크 큐 순서가 와야 실행된다.
  4. Q. requestAnimationFrame을 쓰는 이유는? 브라우저의 화면 갱신 직전에 실행되도록 보장되어 애니메이션이 끊기지 않고, 백그라운드에서는 멈춰 자원을 아낀다.
Gunmo Lee
자바스크립트 이벤트 루프 개념 정리: 콜 스택, 태스크 큐, 마이크로태스크 | Gunmo's Dev Life