queueMicrotask()

Direct access to the queue that promise callbacks use. It exists so you can defer work without the allocation of a promise and without the task-level delay of a timeout.

Global functionHTML standard
Common call
queueMicrotask(() => flush())
Returns
undefined
Replaces
Promise.resolve().then(fn)
Watch out
an endless microtask chain STARVES the event loop
queueMicrotask(callbackcallback — Run after the current task. An exception thrown inside it becomes an uncaught error — there is no way to catch it from the caller.type: Function · required)
→ undefined

Parameters

NameTypeRequiredDescription
callbackFunctionyesRun after the current task. An exception thrown inside it becomes an uncaught error — there is no way to catch it from the caller.

Return value

undefined — Nothing. The callback is queued on the microtask queue and runs once the current task finishes, before any timer and before the browser renders.

Common patterns

Defer without a timer
Runs sooner than setTimeout(fn, 0).
queueMicrotask(() => notifySubscribers());
Batch synchronous updates
Collect now, flush once at the end of the task.
pending.push(change);
if (!scheduled) {
  scheduled = true;
  queueMicrotask(flush);
}
The promise equivalent
Same queue, one extra allocation.
Promise.resolve().then(fn);

Examples

1. Runs after the current task
queueMicrotask(() => console.log("micro")); console.log("sync");
Returns
'sync' then 'micro'
2. Before a timer
setTimeout(() => console.log("t"), 0); queueMicrotask(() => console.log("m"));
Returns
'm' then 't'
3. Same queue as promises
Promise.resolve().then(() => console.log("p")); queueMicrotask(() => console.log("q"));
Returns
'p' then 'q' — FIFO
4. It returns nothing
queueMicrotask(() => {})
Returns
undefined
5. No id, no cancelling
there is no clearMicrotask
Returns
once queued, it runs
6. Errors are uncaught
queueMicrotask(() => { throw new Error("x"); })
Returns
an uncaught error event

Pitfalls

1. An endless chain starves everything
The microtask queue is drained COMPLETELY before the next task, so a microtask that queues another microtask forever blocks timers, rendering and input handling with no way out. A setTimeout loop would yield between iterations; this does not.
Freezes the page
function loop() { queueMicrotask(loop); }
loop();
nothing else ever runs
Yield to tasks
function loop() { setTimeout(loop, 0); }
the page stays responsive
2. It cannot be cancelled
There is no id and no clearMicrotask. Once queued the callback will run, so anything conditional has to be checked inside the callback rather than avoided by cancelling it.
No way to stop it
queueMicrotask(update);   // component unmounts
update still runs
Check inside
queueMicrotask(() => { if (!cancelled) update(); });
guarded
3. Exceptions escape entirely
A throw inside the callback becomes an uncaught error rather than something the caller can handle — there is no promise to reject. If the work can fail, wrap it in a try/catch inside the callback.
Uncaught
try { queueMicrotask(() => { throw new Error("x"); }); } catch {}
the catch never runs
Catch inside
queueMicrotask(() => { try { risky(); } catch (e) { report(e); } });
handled
4. Reaching for it when a timer is correct
Because microtasks run before rendering, using one to yield so the browser can paint does not work — the paint still waits. If you want the UI to update first, you need a task, which means setTimeout or requestAnimationFrame.
No paint
showSpinner();
queueMicrotask(heavyWork);
the spinner never appears
Yield properly
showSpinner();
setTimeout(heavyWork, 0);
the spinner renders first

When to use

Use it
  • Deferring to the end of the current task, before any timer
  • Batching several synchronous changes into one flush
  • Library code that must act after the caller finishes but before anything else
  • Avoiding a promise allocation in a hot path
Reach for something else
  • You want the browser to render first → setTimeout or requestAnimationFrame
  • You need to cancel → a timer, which has an id
  • You need the result → a promise
  • Recursive scheduling → it starves the event loop

Notes

Complexity
O(1) to queue
Return
undefined; the callback runs on the microtask queue, FIFO with promise callbacks
CPython impl
Not V8 — defined by the HTML standard, with an equivalent in Node
Memory
Retains the callback and its closure until it runs
Thread-safe
Single-threaded; the queue is drained fully before the next task

FAQ

Because the thing worth demonstrating is an ORDERING, and that ordering is fixed by the event loop: synchronous code, then every queued microtask, then timers. A demo would print the same array whatever you typed, which is a constant pretending to be a computation. The examples above show the ordering directly.

History

ES2015
Promises introduced the microtask queue to JavaScript, but without direct access.
2018
queueMicrotask added to the HTML standard, exposing the queue directly.