← 레슨JavaScript · TypeScript✍️ The event loop — why a promise beats setTimeout(0)
1 / 45
0 오타
  1import { useState } from "react";
  2 
  3// JavaScript runs ONE thing at a time (it is single-threaded). When async work finishes,
  4// its callback does not run immediately — it waits in a QUEUE. There are two queues, and
  5// they do NOT have equal priority:
  6//
  7//   • microtasks  — Promise .then / queueMicrotask. The engine drains ALL of these first.
🗣 치면서 / 다 치고 — 소리내서 말해

JavaScript is single-threaded: async callbacks wait in queues. Microtasks (promises) are fully drained before any macrotask (setTimeout). So a promise scheduled LATER still runs before a 0ms timer scheduled earlier. Run it and read the order.

Promise.resolve().then(() => out.push("3 microtask"))
A resolved promise's callback is a microtask — it runs after the synchronous code but before any timer.
setTimeout(() => out.push("4 macrotask"), 0)
Even a zero-millisecond timeout is a macrotask, so it waits until every microtask is drained — that's why three prints before four.
⚖️ 트레이드오프도 말해봐
  • A resolved promise's callback runs on the microtask queue, which the event loop fully drains after the current synchronous frame but BEFORE rendering, I/O, or any macrotask, whereas setTimeout(0) is a macrotask clamped to a ~4ms minimum and queued behind already-pending timers and rendering — so a promise wins precisely because microtasks have strictly higher priority than the timer phase.
  • The cost of that priority is starvation: because the loop empties the entire microtask queue before yielding, a chain of promises that keep enqueuing more microtasks can block rendering and starve I/O indefinitely, which is exactly the failure mode setTimeout(0) avoids by surrendering control back to the loop after one task.
  • Reach for queueMicrotask/Promise.resolve().then() when you need to defer work but still land before the next paint — e.g. batching state mutations or guaranteeing a callback runs after the current call stack unwinds without ever yielding to the browser.
  • Use setTimeout(0) (or better, requestAnimationFrame for visual work, or MessageChannel to dodge the 4ms clamp) precisely when you WANT to yield — to let the browser paint, process input, or break up a long task so the main thread stays responsive; choosing a microtask there is the bug, not the optimization.