process.nextTick vs setImmediate
Confusingly named siblings: one is a microtask, the other a loop phase.
The names are historically backwards, so lean on the mechanics instead:
process.nextTick | setImmediate | |
|---|---|---|
| Queue | microtask (nextTick queue) | macrotask (check phase) |
| Runs | before the loop continues, this tick | next time the loop reaches 'check' |
| Can starve the loop? | Yes, if recursive | No β always yields |
| Use it to⦠| fix ordering within the current op | defer real work to the next iteration |
console.log('start');
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
console.log('end');
// start / end / nextTick / immediateThe classic use of nextTick
Library authors use process.nextTick to guarantee an API is consistently asynchronous. If a function sometimes returns synchronously and sometimes via callback, consumers hit "release Zalgo" bugs. Deferring the callback to nextTick makes it always async, without waiting a full loop iteration.
Example
// 'Release Zalgo': an API that is sometimes sync, sometimes async, is a
// footgun. Callers cannot reason about ordering. Fix it with nextTick.
const cache = new Map();
// BAD: synchronous on cache hit, asynchronous on miss.
function getBad(key, cb) {
if (cache.has(key)) return cb(null, cache.get(key)); // sync!
fetchFromDb(key, (err, val) => { cache.set(key, val); cb(err, val); });
}
// GOOD: always asynchronous, so callers can rely on ordering.
function getGood(key, cb) {
if (cache.has(key)) {
const val = cache.get(key);
return process.nextTick(cb, null, val); // deferred, but still this tick
}
fetchFromDb(key, (err, val) => { cache.set(key, val); cb(err, val); });
}
function fetchFromDb(key, cb) { setTimeout(() => cb(null, `row:${key}`), 5); }
getGood('a', (e, v) => console.log('callback', v));
console.log('this line always prints before the callback');When to use it
- A module delays its `'ready'` event with `process.nextTick` so listeners attached synchronously after the constructor receive the event.
- A crawler uses `setImmediate` between page-fetch iterations to allow Node's I/O poll phase to process responses from parallel requests.
- A library validates that `process.nextTick` is chosen over `setImmediate` in a code-review checklist for constructors that emit events.
More examples
nextTick fires before any I/O or timers
Inside an I/O callback, `process.nextTick` still fires before `setImmediate` because nextTick drains immediately.
const fs = require('fs');
fs.readFile(__filename, () => {
process.nextTick(() => console.log('nextTick inside I/O'));
setImmediate( () => console.log('setImmediate inside I/O'));
});
// nextTick fires BEFORE setImmediate even inside an I/O callbackDefer event emission with nextTick
Wrapping `emit` in `nextTick` guarantees the caller's `.on()` call has run before the event fires.
const { EventEmitter } = require('events');
class Server extends EventEmitter {
constructor() {
super();
process.nextTick(() => this.emit('ready'));
}
}
const s = new Server();
s.on('ready', () => console.log('Server ready'));
// Without nextTick, the 'ready' event fires before .on() is calledsetImmediate for safe recursion
Uses `setImmediate` to yield between recursive crawl steps so other I/O callbacks (like incoming responses) can run.
function crawl(urls) {
if (urls.length === 0) return;
const [next, ...rest] = urls;
fetch(next)
.then(r => r.text())
.then(html => {
console.log('Fetched', next);
setImmediate(() => crawl(rest)); // yield to I/O between pages
});
}
crawl(['https://example.com', 'https://example.org']);
Discussion