Penso di poterlo illustrare abbastanza bene. Poiché nextTick
viene chiamato al termine dell'operazione corrente, chiamarlo in modo ricorsivo può finire per impedire al loop degli eventi di continuare. setImmediate
risolve 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 setImmediate
verranno eseguite le chiamate . Quindi, idealmente, la maggior parte di quelle chiamate sarà in realtà abbastanza immediata, non altrettanto immediata di quella nextTick
che 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 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);
Diciamo che abbiamo appena eseguito questo programma e stiamo attraversando la prima iterazione del loop degli eventi. Chiamerà nella step
funzione con iterazione zero. Quindi registrerà due gestori, uno per setImmediate
e uno per process.nextTick
. Chiamiamo quindi ricorsivamente questa funzione dal setImmediate
gestore che verrà eseguito nella successiva fase di controllo. Il nextTick
gestore 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 nextTick
accende al termine dell'operazione corrente, inizia il ciclo dell'evento successivo, si eseguono le normali fasi del ciclo di eventi, si setImmediate
attiva e chiama ricorsivamente la nostra step
funzione per ricominciare il processo. L'operazione corrente termina, nextTick
incendi, 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 step
nel nostro nextTick
gestore 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 step
nel nextTick
gestore, le cose si comporteranno in un ordine diverso. La nostra prima iterazione del ciclo di eventi viene eseguita e chiama la step
registrazione di un setImmedaite
gestore e di un nextTick
gestore. Al termine dell'operazione corrente, il nostro nextTick
gestore emette un incendio che chiama in modo ricorsivo step
e registra un altro setImmediate
gestore e un altro nextTick
gestore. Poiché un nextTick
gestore si attiva dopo l'operazione corrente, la registrazione di un nextTick
gestore all'interno di un nextTick
gestore causerà l'esecuzione del secondo gestore immediatamente al termine dell'operazione del gestore corrente. I nextTick
gestori continueranno a sparare, impedendo che l'attuale loop degli eventi continui. Passeremo attraverso tutto il nostronextTick
gestori prima di vedere un singolo setImmediate
incendio 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 nextTick
chiamate continuerebbero a ripetersi e non permetterebbero che il ciclo di eventi continui alla fase successiva. Questo è il modo in cui nextTick
può diventare bloccante se usato in modo ricorsivo mentre si setImmediate
attiverà nel successivo ciclo di eventi e l'impostazione di un altro setImmediate
gestore 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é nextTick
sembra 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.