← Concepts

阅读中文版本

Microtask Queue

The microtask queue drains promise callbacks and queueMicrotask work completely after each task and before rendering, enabling non-starving async continuation with strict ordering guarantees.

browser

Definition

Microtasks are scheduled units — promise reactions, MutationObserver callbacks, queueMicrotask entries — that run when the JavaScript stack empties, before the event loop proceeds to rendering or the next macrotask. The queue drains fully, so a microtask can enqueue more microtasks and they still all run before the browser paints.

Context and scope

Explains why promise callbacks run before setTimeout callbacks, why infinite microtask loops freeze the page without any paint, and how frameworks batch state updates inside microtasks.

Common misconceptions

  • Microtasks yield to the browser — the queue drains entirely before rendering or timers, so unbounded chains block paint
  • Microtasks run after all tasks — they run at the end of every task, not once per loop iteration
  • Await always creates a macrotask — plain await without timers schedules a microtask continuation

Anti-patterns

  • Recursive promise chains that never let the queue empty, freezing rendering and input
  • Assuming setTimeout(fn, 0) and queueMicrotask are interchangeable orderings for state updates
  • Doing heavy computation in microtasks to hide it from long-task monitoring while still blocking paint

Related concepts

Related examples

Authoritative source

https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide

What it is

Between macrotasks and rendering sits a checkpoint: run all currently queued microtasks, including any they enqueue, until the queue is empty. This gives promise continuations deterministic ordering relative to tasks and paint, which is why a resolved promise callback always beats a setTimeout callback scheduled at the same time.

How it works

Task execution ends; the stack unwinds; the microtask checkpoint drains the queue; then the loop decides whether to render this iteration. Because draining is exhaustive, a self-feeding microtask chain never yields — no paint, no input, no timers — a distinct freeze signature from a busy macrotask loop.

When to care

Ordering-sensitive logic: flushing state batches before paint, implementing scheduler priorities above the primitive, understanding why MutationObserver fires after the current script. Frameworks rely on the drain-before-render property to consolidate mutations into one DOM update per frame.

Pitfalls

Long microtasks still block the frame; moving work into microtasks hides it from long-task metrics without helping users. Mixing microtask and timer sequencing assumptions breaks under different embedder schedules. Break large work across tasks with scheduler.yield-style primitives.