The Event Loop Phases
The event loop cycles through ordered phases to run your callbacks.
The event loop is what lets single-threaded Node handle many tasks. On each cycle it moves through ordered phases, running the callbacks queued in each.
Example
console.log('1: sync');
setTimeout(() => console.log('4: timer'), 0);
setImmediate(() => console.log('5: immediate'));
Promise.resolve().then(() => console.log('3: promise'));
process.nextTick(() => console.log('2: nextTick'));
// Typical order: 1, 2, 3, 4, 5When to use it
- A performance engineer traces why a timer callback fires 50 ms late by understanding that the timers phase only runs after the poll phase drains.
- A server developer ensures that a post-response cleanup function runs in the check phase via `setImmediate` rather than blocking the next I/O cycle.
- A library author uses knowledge of event-loop phases to decide whether to schedule a recursive operation with `process.nextTick` or `setImmediate`.
More examples
Six phases in order
Documents the six event-loop phases and shows that `setImmediate` fires in the check phase, after poll.
// Phases (simplified):
// 1. timers -- setTimeout / setInterval callbacks
// 2. pending I/O -- deferred I/O errors
// 3. idle/prepare -- internal
// 4. poll -- incoming I/O events (most callbacks here)
// 5. check -- setImmediate callbacks
// 6. close -- close event callbacks
setTimeout(() => console.log('timers phase'), 0);
setImmediate(() => console.log('check phase'));
// Output order depends on context, but inside I/O:
// check phase -> timers phaseExecution order demonstration
Inside an I/O callback, `setImmediate` consistently fires before `setTimeout(fn,0)` because the check phase comes before the timers phase re-runs.
const fs = require('fs');
fs.readFile(__filename, () => {
// Inside an I/O callback: setImmediate always before setTimeout
setTimeout( () => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
});
// Output: setImmediate -> setTimeoutnextTick runs before each phase
Shows that `process.nextTick` and Promise microtasks drain before each event-loop phase transition.
process.nextTick(() => console.log('1: nextTick'));
Promise.resolve().then(() => console.log('2: microtask'));
setImmediate( () => console.log('3: setImmediate'));
console.log('0: sync');
// Output: 0 -> 1 -> 2 -> 3
Discussion