Bitlyst

Microtask vs Macrotask in JavaScript — With Node.js vs Browser Notes

Recap (Browser)

  • Microtasks: Promise.then/catch/finally, queueMicrotask, MutationObserver
  • Macrotasks: setTimeout, setInterval, requestAnimationFrame, DOM events
  • Order per tick: run script → drain all microtasks → possibly render → then the next macrotask.

Node.js Event Loop Differences

Node.js has phases in its event loop (simplified):

  1. TimerssetTimeout / setInterval callbacks
  2. Pending Callbacks
  3. Poll → I/O callbacks; may block waiting for I/O
  4. ChecksetImmediate callbacks
  5. Close Callbacks

After each phase, Node runs microtask queues in this order:

  • process.nextTick queue (✅ runs before promise microtasks)
  • Promise microtasks (jobs scheduled by Promise.then / queueMicrotask)

Key takeaways

  • process.nextTick always runs before other microtasks and before moving to the next phase. Overuse can starve the loop.
  • setImmediate runs in the Check phase.
  • setTimeout(..., 0) runs in the Timers phase of a later tick.
  • Order of setTimeout(…,0) vs setImmediate can vary:
    • After I/O, setImmediate tends to run first (since the loop is already at the Check phase).
    • Without I/O, timers may run first (implementation- and timing-dependent).

Example: Node.js ordering

// node-order.js
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise microtask'));
process.nextTick(() => console.log('nextTick microtask'));

Typical Node output:

nextTick microtask
promise microtask
[ either 'immediate' or 'timeout' first, scenario-dependent ]

Why:

  • process.nextTick runs before other microtasks.
  • Promise microtasks run next.
  • Then the loop continues to a phase where either setImmediate or setTimeout fires first.

Tip: If you place the scheduling inside an I/O callback (e.g., fs.readFile), setImmediate is more likely to run before setTimeout.


Example: Browser ordering

<script>
  setTimeout(() => console.log('timeout'), 0);
  Promise.resolve().then(() => console.log('promise microtask'));
  // process.nextTick / setImmediate are Node-only
</script>

Browser output:

promise microtask
timeout

Quick Matrix

API / ConceptBrowser MicrotaskBrowser MacrotaskNode MicrotaskNode Macrotask
Promise.then✔️✔️
queueMicrotask✔️✔️
MutationObserver✔️❌ (browser only)
setTimeout / setInterval✔️✔️ (Timers)
requestAnimationFrame✔️ (render step)
setImmediate✔️ (Check)
process.nextTick✔️ (before other microtasks)

Mental Model 🧠

  • Browser: script → microtasks (Promises) → macrotask (timeout) → render.
  • Node: phase → nextTickPromise microtasks → next phase; setImmediate in Check, timers in Timers.

Use process.nextTick sparingly (it’s too eager), and prefer Promise microtasks for normal “run right after” semantics.


Microtask vs Macrotask — Visual Diagram

This diagram shows how a microtask (Promise) runs before a macrotask (setTimeout) after the script finishes.

Microtask vs Macrotask — Visual Diagram


That’s it — now you can reason about microtasks/macrotasks across both environments with confidence.

How did you like this post?

👍0
❤️0
🔥0
🤔0
😮0
Microtask vs Macrotask in JavaScript — With Node.js vs Browser Notes · Bitlyst