Ich denke, ich kann das ganz gut veranschaulichen. Da nextTickes am Ende der aktuellen Operation aufgerufen wird, kann ein rekursiver Aufruf dazu führen, dass die Ereignisschleife nicht mehr fortgesetzt wird. setImmediateLöst dies, indem in der Prüfphase der Ereignisschleife ausgelöst wird, sodass die Ereignisschleife normal fortgesetzt werden kann.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
Quelle: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Beachten Sie, dass die Überprüfungsphase unmittelbar nach der Abfragephase liegt. Dies liegt daran, dass die Abfragephase und E / A-Rückrufe die wahrscheinlichsten Orte setImmediatesind, an denen Ihre Anrufe ausgeführt werden. Im Idealfall sind die meisten dieser Aufrufe tatsächlich ziemlich unmittelbar, nur nicht so unmittelbar, wie nextTickdies nach jeder Operation überprüft wird und technisch außerhalb der Ereignisschleife vorhanden ist.
Schauen wir uns ein kleines Beispiel für den Unterschied zwischen setImmediateund an 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);
Angenommen, wir haben gerade dieses Programm ausgeführt und durchlaufen die erste Iteration der Ereignisschleife. Es wird die stepFunktion mit der Iteration Null aufgerufen . Anschließend werden zwei Handler registriert, einer für setImmediateund einer für process.nextTick. Wir rufen diese Funktion dann rekursiv vom setImmediateHandler auf, der in der nächsten Prüfphase ausgeführt wird. Der nextTickHandler wird am Ende der aktuellen Operation ausgeführt und unterbricht die Ereignisschleife. Obwohl er als zweiter registriert wurde, wird er tatsächlich zuerst ausgeführt.
Die Reihenfolge lautet: nextTickWird ausgelöst, wenn die aktuelle Operation endet, die nächste Ereignisschleife beginnt, normale Ereignisschleifenphasen ausgeführt werden, wird setImmediateausgelöst und ruft rekursiv unsere stepFunktion auf, um den Prozess erneut zu starten. Der aktuelle Betrieb endet, nextTickBrände usw.
Die Ausgabe des obigen Codes wäre:
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
Verschieben wir nun unseren rekursiven Aufruf stepin unseren nextTickHandler anstelle des 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);
Nachdem wir den rekursiven Aufruf stepin den nextTickHandler verschoben haben, verhalten sich die Dinge in einer anderen Reihenfolge. Unsere erste Iteration der Ereignisschleife wird ausgeführt und ruft die stepRegistrierung eines setImmedaiteHandlers sowie eines nextTickHandlers auf. Nach nextTickBeendigung der aktuellen Operation wird unser Handler ausgelöst, der rekursiv einen stepanderen setImmediateHandler sowie einen anderen nextTickHandler aufruft und registriert . Da ein nextTickHandler nach der aktuellen Operation ausgelöst wird , führt das Registrieren eines nextTickHandlers in einem nextTickHandler dazu, dass der zweite Handler unmittelbar nach Abschluss der aktuellen Handleroperation ausgeführt wird. Die nextTickHandler feuern weiter und verhindern so, dass die aktuelle Ereignisschleife jemals fortgesetzt wird. Wir werden alle unsere durchstehennextTickHandler, bevor wir ein einzelnes setImmediateHandlerfeuer sehen.
Die Ausgabe des obigen Codes lautet:
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
Beachten Sie, dass, wenn wir den rekursiven Aufruf nicht unterbrochen und nach 10 Iterationen abgebrochen nextTickhätten, die Aufrufe immer wieder rekursiv würden und die Ereignisschleife niemals zur nächsten Phase übergehen würde. Auf diese Weise nextTickkann es bei rekursiver Verwendung blockiert werden, während setImmediatedas Auslösen in der nächsten Ereignisschleife und das Festlegen eines anderen setImmediateHandlers innerhalb einer Schleife die aktuelle Ereignisschleife überhaupt nicht unterbricht, sodass die Phasen der Ereignisschleife wie gewohnt weiter ausgeführt werden können.
Ich hoffe, das hilft!
PS - Ich stimme anderen Kommentatoren zu, dass die Namen der beiden Funktionen leicht ausgetauscht werden könnten, da es so nextTickklingt, als würde es in der nächsten Ereignisschleife und nicht am Ende der aktuellen ausgelöst, und das Ende der aktuellen Schleife ist "unmittelbarer" "als der Beginn der nächsten Schleife. Na ja, das bekommen wir, wenn eine API ausgereift ist und die Leute von vorhandenen Schnittstellen abhängig werden.