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.nextTicksetImmediate
Queuemicrotask (nextTick queue)macrotask (check phase)
Runsbefore the loop continues, this ticknext time the loop reaches 'check'
Can starve the loop?Yes, if recursiveNo β€” always yields
Use it to…fix ordering within the current opdefer real work to the next iteration
console.log('start');
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
console.log('end');

// start / end / nextTick / immediate

The 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

Example Β· javascript
// '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.

Example Β· js
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 callback

Defer event emission with nextTick

Wrapping `emit` in `nextTick` guarantees the caller's `.on()` call has run before the event fires.

Example Β· js
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 called

setImmediate for safe recursion

Uses `setImmediate` to yield between recursive crawl steps so other I/O callbacks (like incoming responses) can run.

Example Β· js
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

  • Be the first to comment on this lesson.
process.nextTick vs setImmediate β€” Node.js | SoundsCode