Concept lesson

Promises & Microtask Queue

JavaScript event loop, microtasks vs macrotasks, and async execution order.

lesson
Freshness: current15 min read
Mastery
not started · 0%

Learning outcomes

  • Distinguish microtasks from macrotasks
  • Avoid event loop starvation in browser/Node

Mental model

JavaScript event loops maintain two distinct queues: the Macrotask Queue (setTimeout, setInterval, I/O) and the Microtask Queue (Promise.then, queueMicrotask, process.nextTick).

Synchronous Call Stack
Drain Microtask Queue completely
Execute one Macrotask
RequestAnimationFrame render
Repeat Loop Tick
Conceptual teaching model synthesized from:TypeScript 5.4 Language Specification & Type System Mechanics

Theory

After every synchronous call stack frame empties, the event loop drains the entire Microtask Queue to completion before processing the next macrotask or performing DOM layout repaints. An infinite loop of microtasks (Promise.resolve().then(...)) starves the macrotask queue and freezes browser UI rendering.

export async function traceExecutionOrder(): Promise<string[]> {
  const logs: string[] = [];
  logs.push("1: Sync Start");
  setTimeout(() => logs.push("5: Macrotask (setTimeout)"), 0);

  Promise.resolve().then(() => {
    logs.push("3: Microtask 1");
  }).then(() => {
    logs.push("4: Microtask 2");
  });

  logs.push("2: Sync End");
  await new Promise((resolve) => setTimeout(resolve, 10));
  return logs;
}

Alternatives and trade-offs

setTimeout(fn, 0) schedules execution in the next macrotask tick. queueMicrotask(fn) schedules immediate execution after the current synchronous stack frame without UI rendering delay.

Failure modes and misconceptions

  1. Async/Await is not multithreaded: async functions do not run on background CPU threads; they execute on the main event loop.
  2. Microtask Starvation: Infinite microtask loops block the event loop permanently, preventing UI events or network callbacks from firing.
Reflect before revealing the guide

Decision scenario

When breaking up long-running UI computations, yield using setTimeout or requestAnimationFrame so the browser can process user inputs and render frames between chunks.

Learning outcomes

  • Compare microtask queue priority against macrotask processing.
  • Explain execution order across Promises, async/await, and timers.
  • Prevent microtask queue starvation in high-throughput applications.

Trade-offs

Microtasks execute immediately after synchronous code without render delays, but un-bounded microtask recursion will freeze user interface interactions.

Evidence assessment

Theory and decision mastery

not-started · 0%
theory0%
decision0%
activityNot mapped
projectNot mapped
1. How does the JavaScript event loop prioritize microtasks vs macrotasks?
2. What happens if a recursive function continuously queues microtasks (Promise.resolve().then(...))?
3. Does decorating a function with async run its synchronous body on a background thread?

Decision scenario

A developer needs to break up a large synchronous computation loop so the browser UI stays responsive to user clicks.

Which scheduling approach allows the browser to render frames between chunk batches?

Primary sources