Creo que puedo ilustrar esto bastante bien. Como nextTickse llama al final de la operación actual, llamarla de forma recursiva puede terminar bloqueando la continuación del bucle de eventos. setImmediateresuelve esto disparando en la fase de verificación del bucle de eventos, permitiendo que el bucle de eventos continúe normalmente.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
fuente: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Observe que la fase de verificación es inmediatamente posterior a la fase de sondeo. Esto se debe a que la fase de sondeo y las devoluciones de llamadas de E / S son los lugares más probables en los que setImmediatese ejecutarán sus llamadas . Así que, idealmente, la mayoría de esas llamadas serán bastante inmediatas, pero no tan inmediatas como las nextTickque se verifican después de cada operación y técnicamente existen fuera del bucle de eventos.
Echemos un vistazo a un pequeño ejemplo de la diferencia entre setImmediatey process.nextTick:
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
step(iteration + 1); // Recursive call from setImmediate handler.
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
});
}
step(0);
Digamos que acabamos de ejecutar este programa y estamos pasando por la primera iteración del bucle de eventos. Llamará a la stepfunción con la iteración cero. Luego registrará dos controladores, uno para setImmediatey otro para process.nextTick. Luego llamamos recursivamente a esta función desde el setImmediatecontrolador que se ejecutará en la siguiente fase de verificación. El nextTickcontrolador se ejecutará al final de la operación actual interrumpiendo el bucle de eventos, por lo que, aunque se registró en segundo lugar, se ejecutará primero.
El orden termina siendo: nextTickdispara a medida que finaliza la operación actual, comienza el siguiente bucle de eventos, se ejecutan las fases normales del bucle de eventos, se setImmediatedispara y recursivamente llama a nuestra stepfunción para comenzar el proceso nuevamente. Finaliza la operación actual, nextTickincendios, etc.
La salida del código anterior sería:
nextTick iteration: 0
setImmediate iteration: 0
nextTick iteration: 1
setImmediate iteration: 1
nextTick iteration: 2
setImmediate iteration: 2
nextTick iteration: 3
setImmediate iteration: 3
nextTick iteration: 4
setImmediate iteration: 4
nextTick iteration: 5
setImmediate iteration: 5
nextTick iteration: 6
setImmediate iteration: 6
nextTick iteration: 7
setImmediate iteration: 7
nextTick iteration: 8
setImmediate iteration: 8
nextTick iteration: 9
setImmediate iteration: 9
Ahora pasemos nuestra llamada recursiva a stepnuestro nextTickcontrolador en lugar del setImmediate.
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
step(iteration + 1); // Recursive call from nextTick handler.
});
}
step(0);
Ahora que se ha pasado la llamada recursiva a stepen las nextTickcosas del manejador se comportará en un orden diferente. Nuestra primera iteración del bucle de eventos se ejecuta y llama al stepregistro de un setImmedaitecontrolador y un nextTickcontrolador. Después de que finaliza la operación actual, nuestro nextTickcontrolador se activa stepy llama y registra recursivamente a otro setImmediatecontrolador, así como a otro nextTickcontrolador. Dado que un nextTickcontrolador se dispara después de la operación actual, registrar un nextTickcontrolador dentro de un nextTickcontrolador hará que el segundo controlador se ejecute inmediatamente después de que finalice la operación del controlador actual. Los nextTickcontroladores seguirán disparando, evitando que el ciclo de eventos actual continúe. Vamos a superar todos nuestrosnextTickmanejadores antes de que veamos un solo setImmediatemanejo de fuego.
La salida del código anterior termina siendo:
nextTick iteration: 0
nextTick iteration: 1
nextTick iteration: 2
nextTick iteration: 3
nextTick iteration: 4
nextTick iteration: 5
nextTick iteration: 6
nextTick iteration: 7
nextTick iteration: 8
nextTick iteration: 9
setImmediate iteration: 0
setImmediate iteration: 1
setImmediate iteration: 2
setImmediate iteration: 3
setImmediate iteration: 4
setImmediate iteration: 5
setImmediate iteration: 6
setImmediate iteration: 7
setImmediate iteration: 8
setImmediate iteration: 9
Tenga en cuenta que si no hubiéramos interrumpido la llamada recursiva y la hubiéramos abortado después de 10 iteraciones, las nextTickllamadas seguirían recurriendo y nunca dejarían que el bucle de eventos continuara a la siguiente fase. Así es como nextTickpuede convertirse en bloqueo cuando se usa de forma recursiva, mientras que setImmediatese disparará en el próximo ciclo de eventos y establecer otro setImmediatecontrolador desde dentro no interrumpirá en absoluto el ciclo de eventos actual, lo que le permitirá continuar ejecutando fases del ciclo de eventos de manera normal.
¡Espero que ayude!
PD: estoy de acuerdo con otros comentaristas en que los nombres de las dos funciones podrían intercambiarse fácilmente, ya que nextTickparece que se activará en el siguiente bucle de eventos en lugar del final del actual, y el final del bucle actual es más "inmediato" "que el comienzo del siguiente ciclo. Bueno, eso es lo que obtenemos a medida que una API madura y la gente depende de las interfaces existentes.