개발

JavaScript Event Loop 완전 정리 (Microtask vs Macrotask)

woojin06 2026. 3. 10. 20:45

JavaScript를 처음 배우다 보면 다음과 같은 코드를 마주치게 된다.

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

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

console.log("sync");
 

JavaScript는 싱글 스레드이며 기본적으로 동기적으로 실행된다고 배웠기 때문에
처음 보면 이런 결과를 예상하게 된다.

sync
timeout
promise
 

하지만 실제 실행 결과는 다음과 같다.

sync
promise
timeout

여기서 의문이 생긴다.

setTimeout의 delay가 0인데 왜 Promise가 먼저 실행될까?

이 동작을 이해하려면 JavaScript Event Loop 구조를 알아야 한다.


JavaScript는 싱글 스레드

JavaScript는 Single Thread 기반 언어다.

즉 한 번에 하나의 작업만 처리할 수 있다.

그래서 JavaScript 엔진에는 다음과 같은 실행 구조가 존재한다.

출처: https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
Call Stack

Microtask Queue

Macrotask Queue
 

이 구조를 Event Loop가 계속 순환하면서 실행하게 된다.

실행 우선순위는 다음과 같다.

1️⃣ Call Stack (동기 코드)
2️⃣ Microtask Queue
3️⃣ Macrotask Queue

이 순서를 이해하면 대부분의 JavaScript 비동기 코드를 이해할 수 있다.


Call Stack (동기 코드)

JavaScript 코드는 기본적으로 Call Stack에서 실행된다.

예를 들어 다음 코드를 보자.

console.log(1);
console.log(2);
console.log(3);
 

실행 결과

1
2
3
 

이 코드는 위에서 아래로 순서대로 실행되는 동기 코드다.


Microtask Queue

Microtask는 현재 실행 중인 작업이 끝난 직후 실행되는 작업이다.

대표적인 Microtask

  • Promise.then
  • await
  • queueMicrotask
  • MutationObserver

예시

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

console.log("sync");

결과

sync
microtask
 

실행 과정

1️⃣ 동기 코드 실행
2️⃣ Microtask Queue 실행


Macrotask Queue

Macrotask는 다음 이벤트 루프에서 실행되는 작업이다.

대표적인 Macrotask

  • setTimeout
  • setInterval
  • setImmediate (Node.js)
  • I/O
  • UI Event

예시

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

console.log("sync");

결과

sync
timeout
 

setTimeout은 Macrotask Queue에 등록되기 때문에
동기 코드가 먼저 실행된다.


Microtask vs Macrotask

둘의 가장 중요한 차이는 실행 우선순위다.

위에서 봤듯

Event Loop의 실행 순서는 다음과 같다.

 

Call Stack

Microtask Queue

Macrotask Queue
 

Microtask는 항상 Macrotask보다 먼저 실행된다.

처음 예제로 돌아가 보자.

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

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

console.log("sync");
 

실행 순서

1️⃣ 동기 코드 실행

sync
 

2️⃣ Microtask 실행

promise
 

3️⃣ Macrotask 실행

timeout
 

그래서 최종 결과는

sync
promise
timeout
 

이 된다.


queueMicrotask

Promise 말고도 Microtask를 직접 등록할 수 있는 API가 있다.

바로 queueMicrotask()다.

console.log(1);

queueMicrotask(() => console.log(2));

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

console.log(4);
 

결과

1
4
2
3
 

Microtask는 등록된 순서대로 실행된다.


async / await와 Event Loop

async / await도 Event Loop와 밀접한 관계가 있다.

많은 사람들이 별도의 메커니즘으로 생각하지만
사실 내부적으로는 Promise 기반으로 동작한다.

예시

async function test() {
	console.log(1);
	await Promise.resolve();
	console.log(2);
}

console.log(3);

test();

console.log(4);
 

결과

3
1
4
2
 

동작 과정

1️⃣ 동기 코드 실행

3
 

2️⃣ test() 실행

1
 

3️⃣ await 이후 코드는 Microtask Queue로 이동

4️⃣ 메인 코드 계속 실행

4
 

5️⃣ Microtask 실행

2
 

await 이후 코드는 Microtask로 실행된다.


Event Loop 실행 과정 정리

JavaScript Event Loop는 다음 순서로 동작한다.

1. Call Stack 실행
2. Microtask Queue 전부 실행
3. Rendering
4. Macrotask Queue 실행
 

핵심은 이것이다.

Microtask는 항상 Macrotask보다 먼저 실행된다.

 


React에서 Event Loop가 중요한 이유

프론트엔드 개발에서는 Event Loop 이해가 꽤 중요하다.

특히 다음과 같은 경우에 영향을 준다.

  • 상태 업데이트 batching
  • 데이터 fetching
  • 비동기 로직 순서
  • UI 업데이트 타이밍

예를 들어 다음 코드가 있다.

setTimeout(() => console.log("timeout"));

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

결과

promise
timeout
 

React에서도 Promise 기반 로직이 먼저 실행되는 이유가 바로
Event Loop 우선순위 때문이다.

마무리

JavaScript 비동기 코드를 이해하려면 Event Loop 구조를 이해하는 것이 중요하다.

핵심 정리

1️⃣ JavaScript는 싱글 스레드다
2️⃣ Promise / await는 Microtask다
3️⃣ Microtask는 Macrotask보다 먼저 실행된다

이 구조를 이해하면 다음과 같은 것들이 훨씬 쉽게 이해된다.

  • Promise 체인
  • async / await
  • React 상태 업데이트
  • 비동기 디버깅

JavaScript에서 비동기 코드를 제대로 이해하려면
Event Loop는 반드시 알아야 하는 개념이다.