Suggest an editImprove this articleRefine the answer for “How to add a task to microtask queue with queueMicrotask”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`queueMicrotask`** schedules a callback in the microtask queue, which runs after the current call stack empties but before any `setTimeout` or `setInterval` callbacks. ```javascript console.log("1"); queueMicrotask(() => console.log("2")); // microtask setTimeout(() => console.log("3"), 0); // macrotask console.log("4"); // Output: 1, 4, 2, 3 ``` **Key:** the microtask queue drains completely before any macrotask fires, even `setTimeout(0)`.Shown above the full answer for quick recall.Answer (EN)Image**`queueMicrotask(callback)`** schedules a function in the microtask queue, which the JavaScript event loop processes after the current call stack empties but before any pending macrotasks like `setTimeout` callbacks. ## Theory ### TL;DR - Think of the event loop as a restaurant cashier: macrotasks are full orders in the main line, microtasks are quick "napkin refills" handled right after the current customer, before the next full order starts - `queueMicrotask` runs before `setTimeout(0)`, in the same microtask checkpoint as `Promise.then` - Both fire in FIFO order, so `queueMicrotask` queued before `Promise.then` runs first - Use it for deferred work that should not yield to the macrotask queue - Not for heavy compute: long microtask chains block paint ### Quick example ```javascript console.log("1. Script start"); setTimeout(() => console.log("4. setTimeout"), 0); Promise.resolve().then(() => console.log("3. Promise.then")); queueMicrotask(() => console.log("2. queueMicrotask")); console.log("5. Script end"); // Output: // 1. Script start // 5. Script end // 2. queueMicrotask // 3. Promise.then // 4. setTimeout ``` Sync code runs first. Then all microtasks drain in the order they were queued. `setTimeout` fires last, even with a `0` delay. ### Key difference from Promise.resolve().then() Both `queueMicrotask` and `Promise.resolve().then()` push work into the microtask queue. The wrapper is what differs. `Promise.then` auto-unwraps returned Promises and routes rejections through `.catch`. `queueMicrotask` is a plain callback with no rejection handling: if the callback throws, the error surfaces as an unhandled exception after the current stack, not as a rejected Promise. Pick `queueMicrotask` when you need post-sync timing without the Promise machinery around it. ### When to use - **Batch DOM writes:** defer a style update until all sync state changes finish, then write to the DOM once - **Framework state sync:** run an effect after reactive state settles, but before the browser paints - **Post-sync cleanup:** run teardown code without holding up macrotasks - **Replace `setTimeout(0)` for timing:** when you need true "after current task" without the 4ms timer floor browsers enforce - Avoid it for heavy computation or anything involving I/O. Those belong in macrotasks or [Web Workers](/questions/web-workers) ### How the event loop handles microtasks In V8 (Chrome and Node.js), `queueMicrotask` pushes the callback into a `MicrotaskQueue` tied to the current JavaScript global scope - a linked list of internal `Microtask` objects. After the current macrotask finishes, the [event loop](/questions/event-loop) enters the `RunMicrotasks` phase. It iterates the queue until empty, including any new microtasks added during that drain. Only then does it move to paint or pick up the next macrotask. Browsers implement this via the HTML spec's "microtask checkpoint". Node.js integrates with libuv but follows the same priority. One difference: in Node.js, `process.nextTick` drains before `queueMicrotask` and `Promise.then`, so it runs at a higher priority despite being called a "microtask" in some docs. ### Common mistakes **Assuming `setTimeout` fires first:** ```javascript setTimeout(() => console.log("Timeout"), 0); queueMicrotask(() => console.log("Micro")); // runs first // Output: Micro, Timeout ``` Microtasks drain before any macrotask. If you need `setTimeout` to go first, the two should not be mixed this way. **Recursive `queueMicrotask` with no exit condition:** ```javascript function recurse() { queueMicrotask(recurse); // no base case } queueMicrotask(recurse); // Event loop never exits the microtask phase. Paint freezes. ``` The queue drains until empty. If new microtasks keep arriving, it never leaves. V8 does not auto-stop this. Add a counter or switch to `setTimeout` for work that needs to yield. **Throwing inside and expecting a try/catch outside:** ```javascript try { queueMicrotask(() => { throw new Error("Boom"); }); } catch (e) { console.log("Caught"); // never runs } // Error fires asynchronously, after the current stack ``` Wrap the `try/catch` inside the callback, not around `queueMicrotask` itself. **Mixing up Node.js and browser priority:** ```javascript process.nextTick(() => console.log("nextTick")); queueMicrotask(() => console.log("queueMicrotask")); // Output: nextTick, queueMicrotask ``` In Node.js, `process.nextTick` has higher priority than `queueMicrotask`. In browsers, `process.nextTick` does not exist. For cross-runtime code, use `queueMicrotask` - it follows the spec in both environments. ### Real-world usage - **Vue 3** - `nextTick()` queues DOM updates via `queueMicrotask` after the reactivity system flushes - **React 18** - the scheduler uses `queueMicrotask` for passive effects (post-commit, pre-paint), and automatic batching now extends to native async contexts like `fetch` - **Angular / Zone.js** - patches `queueMicrotask` to track async zones and trigger change detection - **Preact** - `flushSync` defers via `queueMicrotask` for update batching - **Node.js undici** - the HTTP client queues response parsing as a microtask after I/O completes ### Follow-up questions **Q:** What is the output order of sync code, `queueMicrotask`, `Promise.then`, and `setTimeout`? **A:** Sync code runs first. Then all microtasks drain in FIFO order (`queueMicrotask` and `Promise.then` share the same queue, whichever was registered first runs first). `setTimeout` fires last. **Q:** What is the difference between `queueMicrotask` and `Promise.resolve().then()`? **A:** Both schedule work in the microtask queue at the same priority level. `Promise.then` handles rejections and unwraps returned Promises automatically. `queueMicrotask` is a simpler callback with no Promise wrapping and no rejection routing. **Q:** Does `queueMicrotask` block rendering? **A:** Yes. Microtasks drain before the browser paints. A long chain or a recursive `queueMicrotask` will freeze the UI. Use `requestIdleCallback` or `setTimeout` if the work needs to yield to the render pipeline. **Q:** How does `process.nextTick` relate to `queueMicrotask` in Node.js? **A:** Both are microtask-like, but `process.nextTick` has higher internal priority and drains first. `queueMicrotask` follows the WHATWG spec and behaves identically to browsers. For code running in both environments, `queueMicrotask` is the portable choice. **Q:** How does V8 implement the microtask queue internally? **A:** V8 uses a `MicrotaskQueue` stored as a linked list of `v8::internal::Microtask` objects, tied to the current JavaScript global scope. After each macrotask, the event loop calls `MaybeResolvingMicrotaskQueue`, which iterates and runs each entry, including newly added ones, until the list is empty. ## Examples ### Basic: execution order walkthrough ```javascript console.log("sync 1"); queueMicrotask(() => console.log("microtask")); Promise.resolve().then(() => console.log("promise")); setTimeout(() => console.log("timeout"), 0); console.log("sync 2"); // sync 1 // sync 2 // microtask <- registered before Promise.then, fires first // promise // timeout ``` `queueMicrotask` fires before `Promise.then` here because it was registered first. Both live in the same microtask queue, processed in order. ### Intermediate: batching DOM writes ```javascript let count = 0; function increment() { count += 1; count += 1; // multiple sync state mutations queueMicrotask(() => { // single DOM write after all sync updates settle document.getElementById("counter").textContent = count; console.log("DOM updated:", count); // 2 }); } increment(); // One layout calculation instead of two ``` This is the same pattern Vue 3's `nextTick` and React's scheduler use. Group your state mutations synchronously, then write to the DOM once inside the microtask. The browser sees only one change and runs one layout pass. ### Advanced: the infinite microtask trap ```javascript const loopMicrotask = () => { console.log("still running..."); queueMicrotask(loopMicrotask); // re-queues itself }; queueMicrotask(loopMicrotask); // "still running..." logs forever // setTimeout callbacks never fire // browser paint never happens ``` I saw this exact pattern cause a full page hang during a code review, where someone tried to poll a condition with `queueMicrotask` instead of `setInterval`. The event loop drains microtasks completely before moving on. If each microtask adds another, it never moves on. Always add a counter as a guard or use `setTimeout` for polling work that needs to yield.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.