이벤트 루프, 눈으로 따라가기

콜 스택·마이크로태스크 큐·태스크 큐가 어떤 순서로 비워지는지 재생 버튼을 눌러 확인한다.

Seobway · · 8분

자바스크립트는 한 번에 하나만 실행한다. 그런데도 타이머와 네트워크 요청이 화면을 멈추지 않는다. 그 사이를 메우는 게 이벤트 루프다.

말로 읽으면 헷갈린다. 아래를 직접 돌려 봐라. 를 누르거나, 상자를 클릭한 뒤 키를 눌러 한 단계씩 넘길 수 있다.

인터랙티브 다이어그램: 이벤트 루프 한 바퀴 (단계별로 재생할 수 있다)

  1. 스크립트가 실행되며 main()이 콜 스택에 올라간다.
  2. console.log('A') 가 스택에 쌓이고 즉시 실행된다.
  3. setTimeout 은 콜백을 브라우저(Web API)에 맡기고 바로 반환한다.
  4. Promise.then 의 콜백은 마이크로태스크 큐로 예약된다.
  5. 타이머가 끝나 콜백이 태스크 큐로 넘어간다.
  6. main() 이 끝나 콜 스택이 완전히 빈다.
  7. 스택이 비면 이벤트 루프는 마이크로태스크 큐부터 전부 비운다.
  8. 그 다음에야 태스크 큐에서 하나를 꺼낸다.
  9. 큐가 모두 비었다. 다음 틱을 기다린다.

순서를 만드는 규칙 세 가지

함수를 호출하면 스택에 쌓이고, 반환하면 빠진다. 스택이 비어야만 이벤트 루프가 큐를 들여다본다.

그래서 무거운 동기 루프 하나가 타이머 전부를 밀어버린다.

무슨 일이 일어나는가

await 는 문법 설탕이다. 오른쪽 코드는 아래와 같이 읽어야 한다.

  • await 를 만나면 함수가 거기서 멈추고 반환한다.
  • 나머지 몸통은 .then 콜백이 되어 마이크로태스크 큐로 간다.
  • 그래서 await 뒤 줄은 setTimeout(…, 0) 보다 항상 먼저 실행된다.
코드
async function run() {
  console.log("1");
  await null;        // ← 여기서 반환
  console.log("3");  // ← 마이크로태스크로 예약
}

run();
console.log("2");


직접 고쳐 보기

아래 코드의 출력 순서를 먼저 예측하고, 실행해서 맞는지 확인해라. 값을 바꿔 가며 다시 돌려도 된다 (Ctrl+Enter).

console.log("1 동기");

setTimeout(() => console.log("4 타이머"), 0);

Promise.resolve().then(() => console.log("3 마이크로태스크"));

console.log("2 동기");

버튼을 눌러 무거운 동기 루프를 돌려 보면, 그 동안 타이머가 밀리는 걸 눈으로 볼 수 있다.

import { useState } from "react";

export default function App() {
  const [log, setLog] = useState([]);
  const add = (m) => setLog((prev) => [...prev, m]);

  const run = () => {
    setLog([]);
    add("시작");
    setTimeout(() => add("타이머 콜백 (0ms 로 예약했다)"), 0);
    const until = Date.now() + 600;
    while (Date.now() < until) {} // 동기로 600ms 점거
    add("동기 루프 끝 — 이제야 스택이 빈다");
  };

  return (
    <div>
      <button onClick={run}>600ms 막고 타이머 보기</button>
      <ul>{log.map((l, i) => <li key={i}>{l}</li>)}</ul>
    </div>
  );
}

정리

스택이 먼저다

큐는 스택이 빈 뒤에야 열린다. 동기 코드가 길면 비동기도 늦는다.

마이크로 > 매크로

마이크로태스크 큐를 다 비운 다음에 태스크 큐를 하나 꺼낸다.

하나씩

태스크는 한 번에 하나. 그 사이마다 렌더 기회가 생긴다.

참고

  1. [1] Event loop — MDN Web Docs
  2. [2] Event loops — HTML Standard (WHATWG)

관련 글