Read first

The event loop is how a single-threaded runtime like JavaScript handles many things in progress at once. Slow work (timers, network, files) is done outside your code; when it finishes, a callback waits in a queue, and the event loop runs it only when the call stack is empty.

How it works

One turn of the loop in a browser, as the HTML Standard describes it:

  1. Run the oldest task from a task queue until the call stack is empty.
  2. Run every microtask, including microtasks added while doing so, until the microtask queue is empty.
  3. If it is time to render: requestAnimationFrame callbacks, style, layout, paint.
  4. Repeat.
flowchart TD
    T["Take ONE task<br/>(timer, click, I/O, the script)"] --> R["Run it until the<br/>call stack is empty"]
    R --> M{"Microtask queue<br/>empty?"}
    M -- no --> RM["Run the next microtask<br/>(.then, await, queueMicrotask)"]
    RM --> M
    M -- yes --> P{"Time to render?<br/>(~every 16.7 ms)"}
    P -- yes --> D["requestAnimationFrame,<br/>style, layout, paint"]
    P -- no --> T
    D --> T

The small inner loop (microtasks) must finish completely before the big loop can paint or take another task.

Node.js uses the same idea but with named phases (timers, poll for I/O, check for setImmediate, close callbacks), and runs process.nextTick callbacks and promise microtasks between callbacks.

Example

setTimeout(() => console.log('task'), 0);
Promise.resolve().then(() => console.log('microtask'));
console.log('sync');
// sync
// microtask
// task

Traps

  • setTimeout(fn, 0) is a minimum delay, not “now”.
  • Endless microtasks starve rendering; endless tasks do not.
  • Heavy synchronous code blocks everything. Move it to a Web Worker or split it into chunks.

Lesson: #1 · The event loop. Spec: HTML Standard: event loops.

Check yourself

Leads to

  • HTML Standard: event loops: The section of the HTML Standard (WHATWG) that defines how browsers run tasks, microtasks and rendering.
  • Promise: An object that stands for a result that isn’t ready yet. It is pending, then fulfilled with a value or rejected with an error, exactly once.
  • Task queue: Where callbacks for timers, events and I/O wait. The event loop runs one task per turn, then all microtasks.
  • Web Worker: A real background thread for JavaScript in the browser. It has no DOM access and talks to the page by passing messages.