Heim > Artikel > Web-Frontend > Ereignisschleife
Einführung
JavaScript wird meist in einem einzelnen Thread in Node.js und im Browser ausgeführt (mit einigen Ausnahmen wie Worker-Threads, die nicht Gegenstand des aktuellen Artikels sind). In diesem Artikel werde ich versuchen, den Mechanismus der Parallelität bei Node.js zu erklären, der die Ereignisschleife ist.
Bevor Sie mit dem Lesen dieses Artikels beginnen, sollten Sie mit dem Stack und seiner Funktionsweise vertraut sein. Ich habe in der Vergangenheit über diese Idee geschrieben, also schauen Sie sich Stack & Heap an – Beginnen Sie nicht mit dem Codieren, ohne sie zu verstehen – Moshe Binieli | Mittel
Einführungsbild
Beispiele
Ich glaube, dass das Lernen anhand von Beispielen am besten ist, deshalb beginne ich mit 4 einfachen Codebeispielen. Ich werde die Beispiele analysieren und dann in die Architektur von Node.js eintauchen.
Beispiel 1:
console.log(1);
console.log(2);
console.log(3);
// Ausgabe:
// 1
// 2
// 3
Dieses Beispiel ist ziemlich einfach. Im ersten Schritt geht console.log(1) in den Aufrufstapel, wird ausgeführt und dann entfernt. Im zweiten Schritt geht console.log(2) in den Aufrufstapel und wird ausgeführt und dann entfernt und so weiter für console.log(3).
Visualisierung des Aufrufstapels für Beispiel 1
Beispiel 2:
console.log(1);
setTimeout(function foo(){
console.log(2);
}, 0);
console.log(3);
// Ausgabe:
// 1
// 3
// 2
Wir können in diesem Beispiel sehen, dass wir setTimeout sofort ausführen, also würden wir erwarten, dass console.log(2) vor console.log(3) steht, aber das ist nicht der Fall und lassen Sie uns den Mechanismus dahinter verstehen.
Grundlegende Event-Loop-Architektur (wir werden später näher darauf eingehen)
Stack & Heap: Schauen Sie sich meinen Artikel dazu an (ich habe am Anfang dieses Artikels einen Link hinzugefügt)
Web-APIs: Sie sind in Ihren Webbrowser integriert und können Daten aus dem Browser und der umgebenden Computerumgebung offenlegen und damit nützliche komplexe Dinge tun. Sie sind nicht Teil der JavaScript-Sprache selbst, sondern bauen auf der Kern-JavaScript-Sprache auf und bieten Ihnen zusätzliche Superkräfte, die Sie in Ihrem JavaScript-Code verwenden können. Die Geolocation-API bietet beispielsweise einige einfache JavaScript-Konstrukte zum Abrufen von Standortdaten, sodass Sie beispielsweise Ihren Standort auf einer Google-Karte einzeichnen können. Im Hintergrund verwendet der Browser tatsächlich einen komplexen Code auf niedrigerer Ebene (z. B. C++), um mit der GPS-Hardware des Geräts (oder was auch immer zur Bestimmung von Positionsdaten verfügbar ist) zu kommunizieren, Positionsdaten abzurufen und sie zur Verwendung an die Browserumgebung zurückzugeben in Ihrem Code. Aber auch diese Komplexität wird Ihnen durch die API entzogen.
Ereignisschleife und Rückrufwarteschlange: Die Funktionen, die die Web-Apis-Ausführung abgeschlossen haben, werden in die Rückrufwarteschlange verschoben. Dies ist eine reguläre Warteschlangendatenstruktur, und die Ereignisschleife ist dafür verantwortlich, die nächste Funktion aus der Rückrufwarteschlange zu entfernen und die Funktion an zu senden der Aufrufstapel zum Ausführen der Funktion.
Ausführungsreihenfolge
Alle Funktionen, die sich derzeit im Aufrufstapel befinden, werden ausgeführt und dann aus dem Aufrufstapel entfernt.
Wenn der Aufrufstapel leer ist, werden alle in der Warteschlange befindlichen Aufgaben nacheinander auf den Aufrufstapel gelegt und ausgeführt. Anschließend werden sie aus dem Aufrufstapel entfernt.
Lassen Sie uns Beispiel 2 verstehen
Die Methode console.log(1) wird aufgerufen, auf dem Aufrufstapel abgelegt und ausgeführt.
Die setTimeout-Methode wird aufgerufen und auf dem Aufrufstapel platziert und ausgeführt. Diese Ausführung erstellt einen neuen Aufruf an die setTimeout-Web-API für 0 Millisekunden, wenn sie abgeschlossen ist (sofort, oder genauer gesagt, dann). Sagen Sie besser „so schnell wie möglich“). Die Web-API verschiebt den Anruf in die Rückrufwarteschlange.
console.log(3)-Methode wird aufgerufen und auf dem Aufrufstapel platziert und ausgeführt.
Die Ereignisschleife erkennt, dass der Aufrufstapel leer ist, entnimmt die „foo“-Methode aus der Rückrufwarteschlange und platziert sie im Aufrufstapel. Anschließend wird console.log(2) ausgeführt.
Visualisierung des Prozesses für Beispiel 2
Der Verzögerungsparameter in setTimeout(function, delay) steht also nicht für die genaue Zeitverzögerung, nach der die Funktion ausgeführt wird. Es steht für die Mindestwartezeit, nach der die Funktion irgendwann ausgeführt wird.
Beispiel 3:
console.log(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 3500);
setTimeout(function boo() {
console.log(‘boo’);
}, 1000);
console.log(2);
// Ausgabe:
// 1
// 2
// 'buh'
// 'foo'
Visualisierung des Prozesses für Beispiel 3
Beispiel 4:
console.log(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 6500);
setTimeout(function boo() {
console.log(‘boo’);
}, 2500);
setTimeout(function baz() {
console.log(‘baz’);
}, 0);
for (const value of [‘A’, ‘B’]) {
console.log(Wert);
}
Funktion zwei() {
console.log(2);
}
zwei();
// Ausgabe:
// 1
// 'A'
// 'B'
// 2
// 'baz'
// 'buh'
// 'foo'
Visualisierung des Prozesses für Beispiel 4
Die Ereignisschleife führt alle in der Aufgabenwarteschlange wartenden Rückrufe aus. Innerhalb der Aufgabenwarteschlange werden die Aufgaben grob in zwei Kategorien eingeteilt, nämlich Mikroaufgaben und Makroaufgaben.
Makroaufgaben (Aufgabenwarteschlange) und Mikroaufgaben
Genauer gesagt gibt es tatsächlich zwei Arten von Warteschlangen.
Es gibt noch ein paar weitere Aufgaben, die in die Makro-Aufgaben-Warteschlange und die Mikro-Aufgaben-Warteschlange gestellt werden, aber ich werde die häufigsten behandeln.
Gemeinsame Makroaufgaben sind setTimeout, setInterval und setImmediate.
Gängige Mikrotasks sind „process.nextTick“ und „Promise Callback“.
Ausführungsreihenfolge
Alle Funktionen, die sich derzeit im Aufrufstapel befinden, werden ausgeführt und dann vom Aufrufstapel entfernt.
Wenn der Aufrufstapel leer ist, werden alle in der Warteschlange befindlichen Mikroaufgaben nacheinander auf den Aufrufstapel gelegt und ausgeführt. Anschließend werden sie aus dem Aufrufstapel entfernt.
Wenn sowohl die Aufrufliste als auch die Mikroaufgabenwarteschlange leer sind, werden alle in der Warteschlange befindlichen Makroaufgaben nacheinander auf die Aufrufliste gelegt und ausgeführt. Anschließend werden sie aus der Aufrufliste entfernt.
Beispiel 5:
console.log(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 0);
Promise.resolve()
.then(function boo() {
console.log(‘boo’);
});
console.log(2);
// Ausgabe:
// 1
// 2
// 'buh'
// 'foo'
Die Methode console.log(1) wird aufgerufen, auf dem Aufrufstapel platziert und ausgeführt.
SetTimeout wird ausgeführt, das console.log(‘foo’) wird in die SetTimeout-Web-API verschoben und 0 Millisekunden danach in die Makro-Task-Warteschlange.
Promise.resolve() wird aufgerufen, es wird aufgelöst und dann wird die Methode .then() in die Micro-Task-Warteschlange verschoben.
Die Methode console.log(2) wird aufgerufen, auf dem Aufrufstapel platziert und ausgeführt.
Wenn die Ereignisschleife feststellt, dass der Aufrufstapel leer ist, nimmt sie zunächst die Aufgabe aus der Micro-Task-Warteschlange, bei der es sich um die Promise-Aufgabe handelt, legt console.log(‘boo’) auf dem Aufrufstapel ab und führt sie aus.
Die Ereignisschleife erkennt, dass der Aufrufstapel leer ist, dann erkennt sie, dass die Mikroaufgabe leer ist, nimmt dann die nächste Aufgabe aus der Makroaufgabenwarteschlange, die SetTimeout-Aufgabe, und fügt das console.log('foo') ein. auf dem Call-Stack und führt es aus.
Visualisierung des Prozesses für Beispiel 5
Erweitertes Verständnis der Ereignisschleife
Ich habe darüber nachgedacht, über die niedrige Ebene der Funktionsweise des Event-Loop-Mechanismus zu schreiben. Es könnte ein eigener Beitrag sein, also habe ich beschlossen, eine Einführung in das Thema zu bringen und gute Links anzuhängen, die das Thema ausführlich erläutern.
Ereignisschleife auf unterer Ebene erklärt
Wenn Node.js startet, initialisiert es die Ereignisschleife, verarbeitet das bereitgestellte Eingabeskript (oder fügt es in die REPL ein), das asynchrone API-Aufrufe durchführen, Timer planen oder Process.nextTick() aufrufen kann, und beginnt dann mit der Verarbeitung der Ereignisschleife.
Das folgende Diagramm zeigt eine vereinfachte Übersicht über die Reihenfolge der Vorgänge der Ereignisschleife. (Jedes Feld wird als „Phase“ der Ereignisschleife bezeichnet. Schauen Sie sich bitte das Einführungsbild an, um ein gutes Verständnis des Zyklus zu erhalten.)
Vereinfachte Übersicht über die Reihenfolge der Operationen in der Ereignisschleife
Jede Phase verfügt über eine FIFO-Warteschlange mit auszuführenden Rückrufen (ich sage das hier vorsichtig, da es je nach Implementierung möglicherweise eine andere Datenstruktur gibt). Während jede Phase auf ihre eigene Weise etwas Besonderes ist, führt die Ereignisschleife im Allgemeinen beim Eintritt in eine bestimmte Phase alle für diese Phase spezifischen Vorgänge aus und führt dann Rückrufe in der Warteschlange dieser Phase aus, bis die Warteschlange erschöpft ist oder die maximale Anzahl an Rückrufen erreicht ist hat ausgeführt. Wenn die Warteschlange erschöpft ist oder das Rückruflimit erreicht ist, geht die Ereignisschleife zur nächsten Phase über und so weiter.
Phasenübersicht
Timer: In dieser Phase werden von setTimeout() und setInterval() geplante Rückrufe ausgeführt.
Ausstehende Rückrufe: führt E/A-Rückrufe aus, die auf die nächste Schleifeniteration verschoben werden.
Leerlauf, Vorbereiten: nur intern verwendet.
Umfrage: Neue I/O-Ereignisse abrufen; I/O-bezogene Rückrufe ausführen (fast alle mit Ausnahme von Close-Rückrufen, denen, die von Timern geplant werden, und setImmediate()); Der Knoten wird hier gegebenenfalls blockieren.
Überprüfen Sie: Hier werden setImmediate()-Rückrufe aufgerufen.
Close-Callbacks: Einige Close-Callbacks, z.B. socket.on('close', ...).
Wie passen die vorherigen Schritte hier hinein?
Die vorherigen Schritte nur mit der „Callback-Warteschlange“ und dann mit „Makro- und Mikro-Warteschlangen“ waren abstrakte Erklärungen zur Funktionsweise der Ereignisschleife.
Es gibt noch eine weitere WICHTIGE Sache zu erwähnen: Die Ereignisschleife sollte die Mikroaufgabenwarteschlange vollständig verarbeiten, nachdem sie eine Makroaufgabe aus der Makroaufgabenwarteschlange verarbeitet hat.
Schritt 1: Die Ereignisschleife aktualisiert die Schleifenzeit auf die aktuelle Zeit für die aktuelle Ausführung.
Schritt 2: Micro-Queue wird ausgeführt.
Schritt 3: Eine Aufgabe aus der Timer-Phase wird ausgeführt.
Schritt 4: Überprüfen Sie, ob sich etwas in der Mikrowarteschlange befindet, und führen Sie die gesamte Mikrowarteschlange aus, wenn etwas vorhanden ist.
Schritt 5: Kehrt zu Schritt 3 zurück, bis die Timer-Phase leer ist.
Schritt 6: Eine Aufgabe aus der Phase „Ausstehende Rückrufe“ wird ausgeführt.
Schritt 7: Überprüfen Sie, ob sich etwas in der Mikrowarteschlange befindet, und führen Sie die gesamte Mikrowarteschlange aus, wenn etwas vorhanden ist.
Schritt 8: Kehrt zu Schritt 6 zurück, bis die Phase „Ausstehende Rückrufe“ leer ist.
Und dann Leerlauf … Mikro-Warteschlange … Umfrage … Mikro-Warteschlange … Überprüfen … Mikro-Warteschlange … Rückrufe schließen und dann beginnt es von vorne.
Deshalb habe ich einen schönen Überblick darüber gegeben, wie die Event-Schleife tatsächlich hinter den Kulissen funktioniert. Es fehlen viele Teile, die ich hier nicht erwähnt habe, weil die eigentliche Dokumentation einen tollen Job bei der Erklärung leistet, für die ich tolle Links bereitstellen werde Um die Dokumentation zu verstehen, ermutige ich Sie, 10–20 Minuten zu investieren und sie zu verstehen.
Das obige ist der detaillierte Inhalt vonEreignisschleife. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!