microtasks vs macrotasks: how do process.nextTick, Promises, setTimeout, and setImmediate order?
Quick Answer
Macrotasks are event-loop phase callbacks (timers, I/O, setImmediate). Microtasks (process.nextTick and Promise callbacks) run after the current operation and are fully drained before the loop continues. process.nextTick has higher priority than Promise microtasks; setImmediate (check) vs setTimeout(0) (timers) ordering can vary outside an I/O cycle.
Detailed Answer
Answer:
Macrotasks are handled by event-loop phases:
setTimeout/setInterval→ timers phase- I/O completion → poll phase
setImmediate→ check phase
Microtasks run between macrotasks and are drained completely before moving on:
process.nextTickcallbacks (Node-specific, highest priority)- Promise reactions (
.then/catch/finally,awaitcontinuations)
Priority within a tick: current operation → all process.nextTick → all Promise microtasks → next macrotask.
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// nextTick, promise, timeout, immediate (order of first two is guaranteed)
setTimeout(fn, 0) vs setImmediate(fn):
- At the top level, their order is not guaranteed (depends on how fast the loop reaches the timers phase).
- Inside an I/O callback,
setImmediatealways fires before asetTimeout(0), because the check phase comes right after poll:
fs.readFile('f.txt', () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// immediate, then timeout — deterministic here
});
Caution with process.nextTick: because it's drained before anything else, recursively scheduling nextTick can starve the event loop (I/O never gets a turn). Prefer setImmediate when you just want to yield.