Eu acho que posso ilustrar isso muito bem. Como nextTick
é chamado no final da operação atual, chamá-lo recursivamente pode acabar impedindo a continuidade do loop de eventos. setImmediate
resolve isso disparando na fase de verificação do loop de eventos, permitindo que o loop de eventos continue normalmente.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
fonte: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Observe que a fase de verificação ocorre imediatamente após a fase de votação. Isso ocorre porque a fase de pesquisa e os retornos de chamada de E / S são os locais mais prováveis para os quais suas chamadas setImmediate
serão executadas. Portanto, idealmente, a maioria dessas chamadas será realmente imediata, mas não tão imediata quanto a nextTick
que é verificada após cada operação e existe tecnicamente fora do loop de eventos.
Vamos dar uma olhada em um pequeno exemplo da diferença entre setImmediate
e 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 executar este programa e estamos avançando na primeira iteração do loop de eventos. Ele chamará a step
função com a iteração zero. Em seguida, ele registrará dois manipuladores, um para setImmediate
e outro para process.nextTick
. Em seguida, chamamos recursivamente essa função do setImmediate
manipulador que será executado na próxima fase de verificação. O nextTick
manipulador será executado no final da operação atual, interrompendo o loop de eventos; portanto, mesmo que tenha sido registrado em segundo, ele será executado primeiro.
A ordem acaba sendo: nextTick
dispara quando a operação atual termina, o próximo loop de evento começa, as fases normais do loop de evento são executadas, setImmediate
dispara e chama recursivamente nossa step
função para iniciar o processo novamente. A operação atual termina, nextTick
dispara, etc.
A saída do código acima seria:
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
Agora vamos passar nossa chamada recursiva para step
nosso nextTick
manipulador, em vez de 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);
Agora que transferimos a chamada recursiva para step
o nextTick
manipulador, as coisas se comportarão em uma ordem diferente. Nossa primeira iteração do loop de eventos é executada e chama o step
registro de um setImmedaite
manipulador e de um nextTick
manipulador. Após o término da operação atual, nosso nextTick
manipulador é acionado, o que chama step
e registra recursivamente outro setImmediate
manipulador e outro nextTick
manipulador. Como um nextTick
manipulador é acionado após a operação atual, o registro de um nextTick
manipulador em um nextTick
manipulador fará com que o segundo manipulador seja executado imediatamente após o término da operação do manipulador atual. Os nextTick
manipuladores continuarão disparando, impedindo que o loop de eventos atual continue. Vamos passar por todo o nossonextTick
manipuladores antes de vermos um único setImmediate
acionador disparar.
A saída do código acima acaba sendo:
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
Observe que, se não tivéssemos interrompido a chamada recursiva e a abortado após 10 iterações, as nextTick
chamadas continuariam recorrendo e nunca deixando o loop de eventos continuar para a próxima fase. É assim que nextTick
pode ser bloqueado quando usado recursivamente, enquanto setImmediate
dispara no próximo loop de eventos e definir outro setImmediate
manipulador de dentro de um não interrompe o loop de eventos atual, permitindo que continue executando as fases do loop de eventos normalmente.
Espero que ajude!
PS - Eu concordo com outros comentaristas que os nomes das duas funções poderiam ser facilmente trocados, pois nextTick
parece que isso será acionado no próximo loop de eventos, em vez do final do atual, e o final do atual é mais "imediato" "que o início do próximo loop. Bem, é isso que obtemos à medida que a API amadurece e as pessoas dependem das interfaces existentes.