So starten Sie schnell mit VUE3.0: Erfahren Sie,
Obwohl JavaScript Single-Threaded ist, nutzt die Ereignisschleife so viel wie möglich den Systemkernel, sodass Node.js nicht blockierende E/A-Vorgänge ausführen kann. Obwohl die meisten modernen Kernel Multithread-Aufgaben verarbeiten können Hintergrund. Wenn eine Aufgabe abgeschlossen ist, teilt der Kernel Node.js mit, und dann wird der entsprechende Rückruf zur Ausführung hinzugefügt. In diesem Artikel wird dieses Thema ausführlicher erläutert.
Node.js mit der Ausführung beginnt, beginnt die Ereignisschleife wird zuerst initialisiert, verarbeitet das bereitgestellte Eingabeskript (oder fügt es in die REPL ein, was in diesem Dokument nicht behandelt wird). Dadurch wird ein asynchroner API-Aufruf ausgeführt, ein Timer geplant oder Process.nextTick() aufgerufen Starten Sie die Verarbeitung der Ereignisschleife. Die
folgende Abbildung zeigt die vereinfachte Übersicht überdie
Ausführung der Ereignisschleife
┌─>│ Timer │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ ausstehende Rückrufe │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ Leerlauf, vorbereiten │ │ └─────────────┬────────────┘ ┌───────── ───────┐ │ ┌─────────────┴────────────┐ │ eingehend: │ │ │ Umfrage │<─────┤ Verbindungen, │ │ └─────────────┬─────────────┘ │ Daten usw. │ │ ┌─────────────┴────────────┐ └───────── ───────┘ │ │ überprüfen │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ Rückrufe schließen │ └────────────────────────────┘Jede
Box stellt eine Stufe der Ereignisschleife dar.
Jede Stufe verfügt jedoch über eine FIFO-Warteschlangen-Rückrufausführung Jede Stufe wird auf ihre eigene Weise ausgeführt. Wenn die Ereignisschleife in eine Stufe eintritt, führt sie im Allgemeinen alle Vorgänge in der aktuellen Stufe aus und beginnt mit der Ausführung von Rückrufen in der Warteschlange der aktuellen Stufe, bis die Warteschlange vollständig belegt oder ausgeführt ist. die maximalen Daten. Wenn die Warteschlange erschöpft ist oder ihre maximale Größe erreicht, geht die Ereignisschleife zur nächsten Phase über.
In jedem Prozess der Ereignisschleife
Knoten .js prüft, ob er auf asynchrone E/A und einen Timer wartet, und wenn nicht, wirdheruntergefahren
. DetailsEin Timer gibt den kritischen Punkt an, an dem ein Rückruf ausgeführt wird, und nicht den gewünschten Zeitpunkt Der Timer wird so bald wie möglich nach Ablauf der angegebenen Zeit ausgeführt. Allerdings kann die Ausführung durch das Betriebssystem oder andere Rückrufe verzögert werden.
Technisch gesehen bestimmt die Abfragephase, wann der Rückruf ausgeführt wird.
Sie stellen beispielsweise einen Timer so ein, dass er nach 100 ms ausgeführt wird, aber Ihr Skript liest eine Datei asynchron und dauert 95 ms.
const fs = require('fs') ; Funktion someAsyncOperation(callback) { // Angenommen, dies dauert 95 ms fs.readFile('/path/to/file', callback); } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms sind seit meinem Termin vergangen`); }, 100); // someAsyncOperation ausführen, deren Abschluss 95 ms dauert someAsyncOperation(() => { const startCallback = Date.now(); // etwas tun, das 10 ms dauert ... while (Date.now() - startCallback < 10) { // nichts tun } });
Wenn die Ereignisschleife in die Abfragephase eintritt, gibt es eine leere Warteschlange (fs.readFile() ist noch nicht abgeschlossen), sodass die verbleibenden Millisekunden gewartet werden, bis der schnellste Timer-Schwellenwert erreicht ist. Nach 95 ms .readFile() hat das Lesen der Datei abgeschlossen und benötigt 10 ms, um zur Abfragephase hinzuzufügen und die Ausführung abzuschließen. Wenn der Rückruf abgeschlossen ist, befinden sich keine auszuführenden Rückrufe in der Warteschlange und die Ereignisschleife kehrt zur Timer-Phase zurück um den Timer-Rückruf auszuführen. In diesem Beispiel sehen Sie, dass der Timer vor der Ausführung um 105 ms verzögert wird.
Um zu verhindern, dass die Abfragephase die Ereignisschleife blockiert, hat auch libuv (die C-Sprachbibliothek, die die Ereignisschleife und das gesamte asynchrone Verhalten auf der Plattform implementiert) dies getan Eine Poll-Phase. Max. Stoppen der Abfrage weiterer Ereignisse
In dieser Phase werden Rückrufe für bestimmte Systemvorgänge (z. B. TCP-Fehlertypen) ausgeführt. Einige *nix-Systeme möchten beispielsweise darauf warten, dass ein Fehler gemeldet wird, wenn ein TCP-Socket beim Versuch, eine Verbindung herzustellen, ECONNREFUSED empfängt. Dies wird während der ausstehenden Rückrufphase zur Ausführung in die Warteschlange gestellt.
Die Poll-Phase hat zwei Hauptfunktionen
, passieren die folgenden zwei Dinge
Abfragephase
Wenn ja, erreicht die Ereignisschleife die Timer-Phase und führt in dieser Phase die Timer-Rückrufprüfung durch
Ermöglicht es Benutzern, Rückrufe unmittelbar nach Abschluss der Abfragephase auszuführen. Wenn die Abfragephase inaktiv wird und das Skript mit setImmediate() in die Warteschlange gestellt wurde, kann die Ereignisschleife mit der Prüfphase fortfahren, anstatt zu warten.
setImmediate() ist eigentlich ein spezieller Timer, der in einer separaten Phase der Ereignisschleife läuft. Es verwendet eine libuv-API, um Rückrufe zu planen, die nach Abschluss der Abfragephase ausgeführt werden.
Während der Code ausgeführt wird, erreicht die Ereignisschleife normalerweise schließlich die Abfragephase, in der sie auf eingehende Verbindungen, Anforderungen usw. wartet. Wenn jedoch ein Rückruf mit setImmediate() geplant ist und die Umfragephase in den Leerlauf geht, wird sie beendet und mit der Prüfphase fortgefahren, anstatt auf das Umfrageereignis zu warten.
Wenn ein Socket oder eine Operation plötzlich geschlossen wird (z. B. socket.destroy()), wird das Close-Ereignis an diese Stufe gesendet, andernfalls wird es über process.nextTick() setImmediate
setImmediate()
gesendetund setTimeout() sind ähnlich, verhalten sich jedoch unterschiedlich, je nachdem, wann sie aufgerufen werden.
Die Reihenfolge, in der jeder Rückruf ausgeführt wird, hängt davon ab Die Reihenfolge, in der sie aufgerufen werden. Wenn in dieser Umgebung dasselbe Modul gleichzeitig aufgerufen wird, wird die Zeit durch die Leistung des Prozesses begrenzt (dies wird
beispielsweise
auch von anderen Anwendungen beeinflusst, die auf diesem Computer ausgeführt werden).Wenn wir das folgende Skript nicht in E/A ausführen, ist es zwar von der Prozessleistung betroffen, es ist jedoch nicht möglich, die Ausführungsreihenfolge dieser beiden Timer zu bestimmen:
// timeout_vs_immediate.js setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
$ node timeout_vs_immediate.js Time-out sofort $ Knoten timeout_vs_immediate.js sofort timeout
Wenn Sie jedoch in die E/A-Schleife wechseln, wird der sofortige Rückruf immer zuerst ausgeführt
// timeout_vs_immediate.js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
$ node timeout_vs_immediate.js sofort Time-out $ Knoten timeout_vs_immediate.js sofortDer Vorteil von
timeout
setImmediate gegenüber setTimeout besteht darin, dass setImmediate immer zuerst vor jedem Timer in der E/A ausgeführt wird, unabhängig davon, wie viele Timer vorhanden sind.
Obwohl Process.nextTick() Teil der asynchronen API ist, ist Ihnen möglicherweise aufgefallen, dass es nicht im Diagramm angezeigt wird. Dies liegt daran, dass Process.nextTick() nicht Teil der Ereignisschleifentechnologie ist. Der aktuelle Vorgang wird ausgeführt. Nach Abschluss wird nextTickQueue unabhängig von der aktuellen Phase der Ereignisschleife ausgeführt. Hier werden Operationen als Transformationen des zugrunde liegenden C/C++-Handlers definiert und verarbeiten das auszuführende JavaScript. Laut Diagramm können Sie „process.nextTick()“ jederzeit aufrufen. Alle an „process.nextTick()“ übergebenen Rückrufe werden ausgeführt, bevor die Ereignisschleife mit der Ausführung fortfährt. Dies kann zu einigen schlechten Situationen führen, da Sie dadurch rekursiv aufrufen können .process.nextTick() „verhungert“ Ihre E/A, wodurch verhindert wird, dass die Ereignisschleife in die Abfragephase eintritt.
Warum ist diese Situation in Node.js enthalten? Da die Designphilosophie von Node.js darin besteht, dass eine API immer asynchron sein sollte, auch wenn dies nicht unbedingt sein muss, sehen Sie sich die folgende Snippet-
Funktion apiCall(arg, callback) { an. if (typeof arg !== 'string') return process.nextTick( Rückruf, neuer TypeError('Argument sollte ein String sein') ); }
Das Snippet führt eine Parameterprüfung durch und gibt den Fehler bei Fehlern an den Rückruf weiter. Die API wurde kürzlich aktualisiert, um die Übergabe von Parametern an process.nextTick() zu ermöglichen, sodass alle nach dem Rückruf übergebenen Parameter als Argumente für den Rückruf akzeptiert werden können, sodass Sie keine Funktionen verschachteln müssen.
Wir geben den Fehler an den Benutzer zurück, aber nur, wenn wir die Ausführung des restlichen Codes des Benutzers zulassen. Durch die Verwendung von process.nextTick() stellen wir sicher, dass apiCall() seinen Rückruf immer nach dem Rest des Benutzercodes ausführt und bevor die Ereignisschleife fortgesetzt werden kann. Um dies zu erreichen, darf der JS-Aufrufstapel abgewickelt werden und dann wird der bereitgestellte Rückruf sofort ausgeführt, was es ermöglicht, rekursive Aufrufe von „process.nextTick()“ durchzuführen, ohne RangeError zu treffen: Maximale Aufrufstapelgröße überschritten ab Version 8.