Penso di poterlo illustrare abbastanza bene. Poiché nextTickviene chiamato al termine dell'operazione corrente, chiamarlo in modo ricorsivo può finire per impedire al loop degli eventi di continuare. setImmediaterisolve questo problema attivando la fase di controllo del loop degli eventi, consentendo al loop degli eventi di continuare 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/
Si noti che la fase di controllo è immediatamente dopo la fase di polling. Questo perché la fase di polling e i callback I / O sono i luoghi più probabili in cui setImmediateverranno eseguite le chiamate . Quindi, idealmente, la maggior parte di quelle chiamate sarà in realtà abbastanza immediata, non altrettanto immediata di quella nextTickche viene controllata dopo ogni operazione ed esiste tecnicamente al di fuori del ciclo degli eventi.
Diamo un'occhiata a un piccolo esempio della differenza tra setImmediatee 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);
Diciamo che abbiamo appena eseguito questo programma e stiamo attraversando la prima iterazione del loop degli eventi. Chiamerà nella stepfunzione con iterazione zero. Quindi registrerà due gestori, uno per setImmediatee uno per process.nextTick. Chiamiamo quindi ricorsivamente questa funzione dal setImmediategestore che verrà eseguito nella successiva fase di controllo. Il nextTickgestore verrà eseguito al termine dell'operazione corrente interrompendo il ciclo degli eventi, quindi anche se è stato registrato per secondo, verrà effettivamente eseguito per primo.
L'ordine finisce per essere: si nextTickaccende al termine dell'operazione corrente, inizia il ciclo dell'evento successivo, si eseguono le normali fasi del ciclo di eventi, si setImmediateattiva e chiama ricorsivamente la nostra stepfunzione per ricominciare il processo. L'operazione corrente termina, nextTickincendi, ecc.
L'output del codice sopra sarebbe:
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
Ora spostiamo la nostra chiamata ricorsiva stepnel nostro nextTickgestore anziché in 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);
Ora che abbiamo spostato la chiamata ricorsiva stepnel nextTickgestore, le cose si comporteranno in un ordine diverso. La nostra prima iterazione del ciclo di eventi viene eseguita e chiama la stepregistrazione di un setImmedaitegestore e di un nextTickgestore. Al termine dell'operazione corrente, il nostro nextTickgestore emette un incendio che chiama in modo ricorsivo stepe registra un altro setImmediategestore e un altro nextTickgestore. Poiché un nextTickgestore si attiva dopo l'operazione corrente, la registrazione di un nextTickgestore all'interno di un nextTickgestore causerà l'esecuzione del secondo gestore immediatamente al termine dell'operazione del gestore corrente. I nextTickgestori continueranno a sparare, impedendo che l'attuale loop degli eventi continui. Passeremo attraverso tutto il nostronextTickgestori prima di vedere un singolo setImmediateincendio gestore.
L'output del codice precedente finisce per essere:
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
Si noti che se non avessimo interrotto la chiamata ricorsiva e l'abbia interrotta dopo 10 iterazioni, le nextTickchiamate continuerebbero a ripetersi e non permetterebbero che il ciclo di eventi continui alla fase successiva. Questo è il modo in cui nextTickpuò diventare bloccante se usato in modo ricorsivo mentre si setImmediateattiverà nel successivo ciclo di eventi e l'impostazione di un altro setImmediategestore all'interno di uno non interromperà affatto il ciclo di eventi corrente, consentendogli di continuare a eseguire normalmente le fasi del ciclo di eventi.
Spero che aiuti!
PS: concordo con gli altri commentatori sul fatto che i nomi delle due funzioni potrebbero essere facilmente scambiati poiché nextTicksembra che si spari nel prossimo loop dell'evento piuttosto che alla fine di quello corrente, e la fine del loop corrente è più "immediata" "rispetto all'inizio del ciclo successivo. Oh bene, questo è ciò che otteniamo quando un'API matura e le persone dipendono dalle interfacce esistenti.