Asynchronous code and events

Understanding setImmediate()

Understand how Node.js setImmediate() runs a callback in the next event loop iteration, and how it differs from setTimeout(0) and process.nextTick().

setImmediate() schedules a callback for the check phase of a future event-loop iteration:

setImmediate(() => {
  console.log('immediate')
})

It means “run after the current work yields and the loop reaches the check phase.” It does not mean “run immediately” and it does not mean “run after exactly zero milliseconds.”

Compare the three queues:

  • process.nextTick() runs after the current JavaScript operation, before the event loop continues to another phase.
  • setTimeout(callback, 0) schedules a timer after a minimum delay threshold. The operating system and other work can delay it.
  • setImmediate() runs in the check phase, after the poll phase used for I/O callbacks.

From the top level of a script, the relative order of a zero-delay timeout and an immediate should not be treated as a contract:

setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))

Inside an I/O callback, the immediate is scheduled for the check phase following that poll work and normally runs before a newly scheduled zero-delay timer:

import { readFile } from 'node:fs'

readFile(import.meta.filename, () => {
  setTimeout(() => console.log('timeout'), 0)
  setImmediate(() => console.log('immediate'))
})

process.nextTick() is not an ordinary event-loop phase. Recursively scheduling next-tick callbacks can starve timers and I/O because Node drains that queue before continuing. Use it for narrow API-ordering needs, not for breaking up large computations.

An Immediate normally keeps the process alive. Calling .unref() allows the process to exit if that callback is the only remaining work:

setImmediate(() => console.log('may run')).unref()

Ordering details can evolve with Node and its event-loop implementation. Write code around documented phase relationships, not a race you observed once on your machine.

Try this on your own machine: run the top-level and I/O examples ten times on your Node version. Record the output. Then add five recursive process.nextTick() calls and notice why an unbounded version would delay other work.

Lesson completed