¿Cuál es el orden de ejecución entre `process.nextTick`, las promesas (`.then`), `setImmediate` y `setTimeout` en Node.js?
Ver respuesta — intenta responderla en voz alta primero
La prioridad, para el código en el nivel superior del módulo, es: primero el código síncrono, luego la cola de process.nextTick, luego las microtareas de Promises (.then), y por último el event loop entra a sus fases donde corren timers (setTimeout) y check (setImmediate). El orden relativo entre setTimeout(fn, 0) y setImmediate no está garantizado en el nivel superior. Hay que cuidar el starvation: si process.nextTick se llama en bucle, puede impedir que el event loop avance.
Node tiene dos "colas rápidas" que se vacían entre cada fase del event loop (y al terminar el script inicial), antes de pasar a la siguiente fase:
process.nextTick: la de máxima prioridad. Todo lo que metas aquí corre antes que cualquier otra cosa asíncrona.- Microtareas de Promises: los
.then,.catch,.finallyy lo que sigue a unawait. Se vacían después de la cola denextTick.
Y tiene las fases del event loop, donde viven:
setTimeout/setInterval: corren en la fase de timers.setImmediate: corre en la fase de check.
Entonces, para código que se registra en el nivel superior del módulo, el orden es:
Sincrono -> process.nextTick -> Promises (.then) -> [event loop: timers y check]
Sobre setTimeout(0) vs setImmediate: en el nivel superior su orden no es determinista. A veces sale primero uno, a veces el otro, porque depende de cuánto tarde Node en arrancar el loop. Por eso en los ejemplos deterministas conviene no mezclarlos, o aclarar que su orden puede variar. (Dentro de un callback de I/O sí es determinista: ahí setImmediate siempre gana, porque tras la fase poll viene directamente la fase check.)
Cuidado con el starvation (inanición) de nextTick: como la cola de nextTick se vacía por completo antes de continuar, si dentro de un callback de nextTick vuelves a llamar a process.nextTick, y ese a otro, etc., creas un bucle infinito de microtareas que nunca deja avanzar al event loop. El servidor deja de procesar timers, I/O y peticiones. Para "ceder" el turno al event loop suele ser mejor setImmediate.
// Ejemplo con orden 100% determinista (sin mezclar 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)
Explicación de la salida:
1y2son síncronos: corren en orden inmediato.3esprocess.nextTick: se vacía primero, antes que las promesas.4y5son microtareas de Promises: se vacían después denextTick, en el orden en que se registraron.6essetTimeout: espera a que el event loop entre a la fase de timers, que ocurre al final.
Ejemplo del riesgo de starvation (NO ejecutes esto en un servidor real):
// Este bucle de nextTick NUNCA deja avanzar al event loop.
function bucleInfinito() {
process.nextTick(bucleInfinito); // se re-agenda a si mismo sin parar
}
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);
Usar process.nextTick cuando en realidad querías setImmediate. Como nextTick tiene máxima prioridad y se vacía por completo antes de continuar, si lo usas para trabajo recurrente puedes provocar starvation del event loop. La regla práctica: usa process.nextTick solo para cosas muy puntuales (por ejemplo, asegurar que un callback se ejecute de forma asíncrona pero cuanto antes); si quieres "dejar respirar" al event loop, usa setImmediate.
"En el nivel superior del módulo, primero corre todo el código síncrono, luego se vacía la cola de process.nextTick, que tiene la máxima prioridad, después las microtareas de las promesas, y hasta entonces el event loop entra a sus fases, donde corren los setTimeout en la fase de timers y los setImmediate en la fase de check. Entre setTimeout de cero y setImmediate el orden no está garantizado en el nivel superior; sí lo está dentro de un callback de I/O, donde setImmediate siempre gana. Y un detalle clave: hay que tener cuidado con el starvation de process.nextTick, porque si lo re-agendas en bucle nunca dejas avanzar al event loop; para ceder el turno prefiero setImmediate."
¿En qué orden se imprimen las líneas?
console.log("A");
process.nextTick(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
process.nextTick(() => console.log("D"));
console.log("E");
Ver respuesta
El orden es: A, E, B, D, C.
AyEson síncronos y corren de inmediato, en orden.- La cola de
process.nextTickse vacía completa antes que las promesas, respetando el orden de registro: primeroB, luegoD. Ces una microtarea de Promise, que se vacía después de toda la cola denextTick.