Node.js path · Node's loop · Topic 5 of 8

process.nextTick vs promises

Not started

process.nextTick(fn) queues fn to run as soon as the current JavaScript operation finishes, before Node goes back to the event loop. Node keeps this queue apart from the promise microtask queue and, after each callback, empties the nextTick queue first and the promise queue second.

Why it matters: Libraries use it to call you back asynchronously but as soon as possible; used carelessly it can starve all I/O.

  1. 1nextTicks first

    When a callback (or the main script) returns, Node runs every queued process.nextTick callback, including ones added on the way.

  2. 2Then every microtask

    Next V8 drains the promise queue completely. A nextTick queued from a promise callback waits until that drain is over.

  3. 3Repeat until both are empty

    Node goes back and forth between the two queues until neither has anything left. Only then does the loop move on.

  4. 4Then the phases

    Timers, polling for I/O, then the check phase where setImmediate callbacks run, and round again.

1

Watch it run

Pick a snippet, then play it or step through. Every step says what just happened.

Promise.resolve().then(() => {  console.log("promise 1");  process.nextTick(() => console.log("tick from promise"));});process.nextTick(() => {  console.log("tick 1");  Promise.resolve().then(() => console.log("promise from tick"));});Promise.resolve().then(() => console.log("promise 2"));setTimeout(() => console.log("timeout"), 0);console.log("sync");

Call stack

What is running right now

empty

Timers

setTimeout callbacks waiting to be due

empty

nextTick queue

Node's own queue: drained first

empty

Microtask queue

V8's queue: promise callbacks

empty

Check (setImmediate)

Runs once per loop turn, after polling for I/O

empty

Console0 lines

    Step 0 of 16

    Nothing has run yet. Node runs a CommonJS file as one ordinary function call.

    2

    Check yourself

    3 questions. Get them all right to finish this topic.

    1. 1.In a CommonJS file, which line prints first?
      Promise.resolve().then(() => console.log("promise"))process.nextTick(() => console.log("nextTick"))
    2. 2.Run the same two lines as an .mjs file and the order flips. Why?
    3. 3.What does the Node documentation recommend for most code that needs to run "right after this"?

    0 of 3 right so far. Get all three to finish this topic.

    CommonJS or ES module

    The first two snippets are the same code. A CommonJS file (.cjs, or .js without "type": "module") returns to Node when it ends, and Node drains nextTicks before promises. An ES module's top-level code runs from inside a promise job, so the promise queue is already being drained when it ends, and the nextTick queue only gets its turn after that. Code that depends on the exact order of these two queues is fragile.

    What it is for

    The classic use is letting a caller finish setting up before you call back. An object that emits an event from its constructor would emit before anyone could attach a listener; deferring with process.nextTick fixes that while still running before any I/O.

    import { EventEmitter } from "node:events" class Job extends EventEmitter {  constructor() {    super()    process.nextTick(() => this.emit("ready")) // listeners attached below still hear it  }}new Job().on("ready", () => console.log("ready"))

    The danger

    The nextTick queue is drained completely, including callbacks added while draining. A function that keeps rescheduling itself with process.nextTick stops the loop from ever reaching the poll phase, so no I/O completes and no timer fires. The same is true for a microtask that keeps queueing microtasks. For new code, prefer queueMicrotask, and use setImmediate when you want to yield to I/O between pieces of work.