What is the execution order between `process.nextTick`, promises (`.then`), `setImmediate` and `setTimeout` in Node.js?
Show answer — try answering out loud first
The priority, for code at the top level of the module, is: first the synchronous code, then the process.nextTick queue, then the Promise microtasks (.then), and finally the event loop enters its phases where timers (setTimeout) and check (setImmediate) run. The relative order between setTimeout(fn, 0) and setImmediate is not guaranteed at the top level. You have to watch out for starvation: if process.nextTick is called in a loop, it can prevent the event loop from advancing.
Node has two "fast queues" that are drained between each phase of the event loop (and when the initial script finishes), before moving to the next phase:
process.nextTick: the highest priority one. Everything you put here runs before any other asynchronous thing.- Promise microtasks: the
.then,.catch,.finallyand whatever follows anawait. They are drained after thenextTickqueue.
And it has the event loop phases, where these live:
setTimeout/setInterval: run in the timers phase.setImmediate: runs in the check phase.
So, for code that is registered at the top level of the module, the order is:
Sincrono -> process.nextTick -> Promises (.then) -> [event loop: timers y check]
About setTimeout(0) vs setImmediate: at the top level their order is not deterministic. Sometimes one comes out first, sometimes the other, because it depends on how long Node takes to start the loop. That's why in deterministic examples it's best not to mix them, or to clarify that their order may vary. (Inside an I/O callback it IS deterministic: there setImmediate always wins, because after the poll phase comes the check phase directly.)
Watch out for nextTick starvation: since the nextTick queue is drained completely before continuing, if inside a nextTick callback you call process.nextTick again, and that one another, etc., you create an infinite loop of microtasks that never lets the event loop advance. The server stops processing timers, I/O and requests. To "yield" the turn to the event loop it's usually better to use setImmediate.
// Example with 100% deterministic order (without mixing setTimeout vs setImmediate).
console.log("1: sincrono inicio");
setTimeout(() => {
console.log("6: setTimeout 0 (fase timers)");
}, 0);
Promise.resolve().then(() => {
console.log("4: promise .then (microtarea)");
});
process.nextTick(() => {
console.log("3: process.nextTick (maxima prioridad)");
});
Promise.resolve().then(() => {
console.log("5: segunda promise .then");
});
console.log("2: sincrono fin");
// Salida:
// 1: sincrono inicio
// 2: sincrono fin
// 3: process.nextTick (maxima prioridad)
// 4: promise .then (microtarea)
// 5: segunda promise .then
// 6: setTimeout 0 (fase timers)
Explanation of the output:
1and2are synchronous: they run in immediate order.3isprocess.nextTick: it drains first, before the promises.4and5are Promise microtasks: they drain afternextTick, in the order they were registered.6issetTimeout: it waits for the event loop to enter the timers phase, which happens at the end.
Example of the starvation risk (do NOT run this in a real server):
// Este bucle de nextTick NUNCA deja avanzar al event loop.
function bucleInfinito() {
process.nextTick(bucleInfinito); // it re-schedules itself endlessly
}
bucleInfinito();
// Este setTimeout JAMAS se ejecutara, porque la cola de nextTick
// nunca se vacia y el event loop nunca llega a la fase de timers.
setTimeout(() => console.log("nunca se imprime"), 0);
Using process.nextTick when you actually wanted setImmediate. Since nextTick has the highest priority and is drained completely before continuing, if you use it for recurring work you can cause starvation of the event loop. The practical rule: use process.nextTick only for very specific things (for example, ensuring a callback runs asynchronously but as soon as possible); if you want to "let the event loop breathe", use setImmediate.
"At the top level of the module, first all the synchronous code runs, then the process.nextTick queue is drained, which has the highest priority, then the promise microtasks, and only then the event loop enters its phases, where the setTimeout callbacks run in the timers phase and the setImmediate ones in the check phase. Between setTimeout of zero and setImmediate the order is not guaranteed at the top level; it is guaranteed inside an I/O callback, where setImmediate always wins. And a key detail: you have to be careful with process.nextTick starvation, because if you re-schedule it in a loop you never let the event loop advance; to yield the turn I prefer setImmediate."
In what order are the lines printed?
console.log("A");
process.nextTick(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
process.nextTick(() => console.log("D"));
console.log("E");
See answer
The order is: A, E, B, D, C.
AandEare synchronous and run immediately, in order.- The
process.nextTickqueue is drained completely before the promises, respecting the registration order: firstB, thenD. Cis a Promise microtask, which is drained after the entirenextTickqueue.