BioPlayground

🧬
목록으로

이벤트 루프 — JavaScript가 비동기를 처리하는 방법

JavaScript 이벤트 루프의 동작 원리, 콜 스택과 태스크 큐의 관계, 마이크로태스크 우선순위를 설명합니다.

중급
|
5
|
검증 완료 (2026-07)
진행률0/55 (0%)

이벤트 루프 — JavaScript가 비동기를 처리하는 방법

이 토픽을 마치면

JavaScript가 싱글 스레드이면서 비동기를 처리할 수 있는 원리를 이해하게 됩니다.


싱글 스레드인데 어떻게?

JavaScript는 한 번에 하나의 작업만 실행합니다. 싱글 스레드입니다. 그런데 타이머, 네트워크 요청, 클릭 이벤트를 동시에 처리합니다. 어떻게?

비밀은 **이벤트 루프(Event Loop)**에 있습니다. JavaScript 엔진(V8)이 혼자 하는 게 아닙니다. 브라우저(또는 Node.js)가 도와줍니다.


세 가지 구성 요소

콜 스택(Call Stack): 현재 실행 중인 함수가 쌓이는 곳입니다. 함수를 호출하면 쌓이고, 실행이 끝나면 빠집니다. LIFO(Last In, First Out) 구조입니다.

Web API: 브라우저가 제공하는 기능입니다. setTimeout, fetch, DOM 이벤트 리스너 등. JavaScript 엔진 밖에서 동작합니다.

태스크 큐(Task Queue): Web API에서 완료된 콜백이 대기하는 줄입니다. 콜 스택이 비면 여기서 하나씩 꺼내서 실행합니다.


실행 순서

javascript
console.log("1");

setTimeout(() => {
  console.log("2");
}, 0);

console.log("3");

출력: 1, 3, 2.

0ms 타이머인데도 "2"가 마지막입니다. 흐름을 따라가면:

  1. console.log("1") → 콜 스택에서 실행 → 출력
  2. setTimeout → Web API에 타이머 등록 → 콜 스택에서 빠짐
  3. console.log("3") → 콜 스택에서 실행 → 출력
  4. 콜 스택 비어있음 → 이벤트 루프가 태스크 큐 확인 → 콜백 실행 → "2" 출력

setTimeout(fn, 0)은 "지금 당장"이 아니라 "콜 스택이 비면 그때"라는 뜻입니다.


마이크로태스크 큐

Promise의 .then()은 태스크 큐가 아니라 **마이크로태스크 큐(Microtask Queue)**에 들어갑니다. 마이크로태스크는 태스크보다 먼저 실행됩니다.

javascript
console.log("1");

setTimeout(() => console.log("2"), 0);

Promise.resolve().then(() => console.log("3"));

console.log("4");

출력: 1, 4, 3, 2.

콜 스택이 비면 이벤트 루프는:

  1. 먼저 마이크로태스크 큐를 전부 비웁니다 (Promise 콜백)
  2. 그 다음 태스크 큐에서 하나를 꺼냅니다 (setTimeout 콜백)

async/await도 내부적으로 Promise를 쓰므로 마이크로태스크입니다.


콜 스택을 오래 점유하면

javascript
while (true) {
  // 무한 루프
}

콜 스택이 비지 않으면 이벤트 루프가 돌지 않습니다. 클릭해도 반응 없고, 타이머도 실행 안 됩니다. 브라우저가 "이 페이지가 응답하지 않습니다"를 표시합니다.

무거운 계산을 해야 하면 Web Worker를 쓰거나, 작업을 쪼개서 콜 스택을 주기적으로 비워줘야 합니다.


핵심

JavaScript는 싱글 스레드지만, 브라우저의 Web API와 이벤트 루프로 비동기를 처리합니다. setTimeout(fn, 0)은 "콜 스택이 비면 실행"이라는 뜻입니다. **마이크로태스크(Promise)**는 태스크(setTimeout)보다 항상 먼저 실행됩니다.