📚 cs/자바스크립트

[JavaScript] 이벤트 루프 (Event Loop)

dev.daisy 2025. 9. 4. 12:56

 

자바스크립트는 흔히 싱글 스레드 기반 언어라고 불립니다. 즉 동시에 하나의 작업만 처리할 수 있다는 뜻입니다. 하지만 실제 브라우저 환경을 떠올려보면, 웹 페이지는 애니메이션을 실행하면서 동시에 사용자 입력을 받고, 네트워크 요청도 처리합니다. Node.js 서버 역시 수많은 HTTP 요청을 동시에 처리합니다.

'스레드는 하나인데, 어떻게 이런 일이 가능할까?'의 해답이 바로 이벤트 루프(Event Loop)입니다.

이벤트 루프란?

자바스크립트의 이벤트 루프(Event Loop)는 싱글 스레드 기반 엔진과 여러 스레드를 사용하는 실행 환경을 연결하는 핵심 장치입니다.

JS 엔진 자체는 단일 호출 스택(Call Stack)을 사용해 한 번에 한 작업만 처리할 수 있습니다. 그러나 브라우저나 Node.js 같은 환경에서는 여러 스레드가 동시에 동작하며 I/O, 타이머, 네트워크 요청 등을 처리합니다.

 

이 이벤트 루프가 비동기 작업의 결과를 콜 스택으로 전달하여, JS가 동기 코드와 비동기 코드를 효율적으로 실행할 수 있게 합니다.

“JS가 싱글 스레드라도 비동기 코드를 동시에 처리할 수 있는 이유”가 바로 이벤트 루프입니다.

 

이벤트 루프의 동작 순서는?
요약: 태스크 큐 → 이벤트 루프 → 호출 스택
[Web APIs] --(완료된 콜백)--> [Task Queue] --(이벤트 루프 확인)--> [Call Stack]


태스크 큐(Tast Queue): 완료된 비동기 콜백들이 대기하는 공간
이벤트 루프(Event Loop): 항상 호출 스택을 감시하며, 스택이 비면(=동기 코드의 실행이 끝나면) 태스크 큐에서 하나를 꺼내 스택으로 전달
호출 스택(Call Stack): 자바스크립트 엔진이 실제 실행하는 공간으로 항상 스택이 비었을 때만 새로운 작업을 가져올 수 있음


즉 이벤트 루프는 직접 실행을 하는게 아니라, 큐에서 스택으로 태스크를 옮겨주는 중재자 역할입니다.

 

단일 호출 스택과 Run-to-Completion

자바스크립트는 싱글 스레드 언어이기 때문에 단일 호출 스택(Call Stack)을 사용합니다. 이는 동시에 여러 작업을 처리하지 않고, 한 번에 하나의 실행 컨텍스트만 처리할 수 있다는 의미입니다. 

 

이 때 중요한 실행 원칙이 바로 Run-to-Completion입니다. Run-to-Completion은 '하나의 실행 컨텍스트가 실행되기 시작하면, 스택이 완전히 비워질 때까지 다른 코드가 끼어들 수 없다'는 자바스크립트의 실행 보장 규칙입니다.

 

이벤트 루프(Event Loop)와 Run-to-Completion의 관계를 설명해주세요.
이벤트 루프는 단일 호출 스택이 비워졌을 때만 큐에 있는 작업을 가져와 실행합니다. 즉 Run-to-Completion 원칙이 보장되기 때문에, 한 작업이 끝나지 않으면 다음 이벤트는 실행되지 않습니다.
 
이 규칙 덕분에 코드의 실행 순서를 확정적으로 이해할 수 있지만, 무거운 작업이 있으면 이벤트 루프의 사이클이 늦어져 UI 렌더링이나 이벤트 처리가 막히는 블로킹 이슈가 생길 수 있습니다.

 

예를 들어 아래와 같은 코드의 실행 결과는 다음과 같습니다.

function delay() {
  for (let i = 0; i < 100000; i++);
}
function foo() {
  delay();
  bar();
  console.log("foo!");
}
function bar() {
  delay();
  console.log("bar!");
}
function baz() {
  console.log("baz!");
}

setTimeout(baz, 10);
foo();
bar!
foo!
baz!

 

여기서 setTimeout(baz, 10)이 먼저 호출되었더라도, foo()와 bar()가 끝날 때까지 baz()는 실행되지 않습니다. 이유는 간단한데, 호출 스택이 완전히 비워져야 이벤트 루프가 큐에서 새로운 태스크(baz)를 가져올 수 있기 때문입니다.


태스크 큐(Task Queue)와 이벤트 루프

그렇다면 baz()는 어디서 기다리고 있을까요? 바로 태스크 큐(Task Queue)입니다.

  • 태스크 큐: setTimeout, setInterval, I/O 콜백 등이 대기하는 큐
  • 마이크로태스크 큐: Promise.then, MutationObserer, queueMicrotask 등이 대기하는 큐

이벤트 루프는 호출 스택이 비워질 때마다 큐에서 태스크를 꺼내서 실행하는데, 코드로 표현해보면 다음과 같습니다.

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

 

즉, 이벤트 루프는 끊임없이 '스택이 비었는지, 큐에 태스크가 있는지'를 확인하며, 스택이 비면 새로운 콜백을 실행하는 구조입니다.

 

또한 호출 스택이 비워질 때마다 마이크로태스크 → 태스크 순서로 작업을 실행합니다.

console.log("start");

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

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

console.log("end");
start
end
promise
setTimeout

이벤트 루프의 실사용 예시

1) Promise vs setTimeout

console.log("start");

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

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

console.log("end");
start
end
promise
setTimeout
  • console.log("start")console.log("end")동기 코드이므로 가장 먼저 실행됩니다.
  • setTimeout 콜백은 매크로태스크 큐에 들어가고, Promise.then 콜백은 마이크로태스크 큐에 들어갑니다.
  • 이벤트 루프는 마이크로태스크를 먼저 처리하기 때문에 "promise""setTimeout"보다 먼저 출력됩니다.

 

 

2) Heavy task가 UI를 막는 경우

document.querySelector("button").addEventListener("click", () => {
  let start = Date.now();
  while (Date.now() - start < 3000) {} // 3초 동안 블로킹
  console.log("버튼 클릭 처리 완료");
});

 

  • 버튼 클릭 이벤트 리스너가 실행되면 호출 스택에서 3초 동안 while 루프가 계속 실행됩니다.
  • 이 동안 이벤트 루프는 새로운 이벤트(다른 클릭, 렌더링, 네트워크 응답 등)를 가져올 수 없어 UI가 멈춘 것처럼 보입니다.
  • 따라서 무거운 연산은 Web Worker로 분리하거나, setTimeout/queueMicrotask 등으로 나눠 실행해야 브라우저가 중간에 렌더링을 처리할 수 있습니다.

보조 개념 정리

      • 호출 스택(Call Stack): 현재 실행 중인 함수의 실행 컨텍스트를 저장하는 자료구조
      • 태스크 큐(Task Queue, 매크로태스크 큐): setTimeout, setInterval, setImmediate(Node.js) 같은 비동기 콜백이 들어가는 큐
      • 마이크로태스크 큐(Microtask Queue): Promise.then, catch, finally, MutationObserver, queueMicrotask 같은 콜백이 들어가는 큐, 우선순위가 태스크 큐보다 높아 스택이 비면 먼저 처리

References

https://ui.toast.com/weekly-pick/ko_20160617?utm_source=chatgpt.com

https://ui.toast.com/posts/ko_20220725?utm_source=chatgpt.com#글을-쓰게-된-계기

https://engineering.linecorp.com/ko/blog/dont-block-the-event-loop?utm_source=chatgpt.com