Die Reihenfolge der Ereignisausführung in der Knotenereignisschleife

Die Reihenfolge der Ereignisausführung in der Knotenereignisschleife

Ereignisschleife

In der Browserumgebung verfügt unser js über eine eigene Ereignisschleife, und in der Knotenumgebung gibt es auch eine ähnliche Ereignisschleife.

Ereignisschleife der Browserumgebung

Sehen wir uns zunächst die Ereignisschleife im Browser an:

Zusammenfassend:

Zuerst wird der synchrone Code des Hauptthreads ausgeführt. Jede Zeile synchronen Codes wird in den Ausführungsstapel geschoben, und jede Zeile asynchronen Codes wird in die asynchrone API (wie Timer-Thread, Ajax-Thread usw.) geschoben. Wenn im Ausführungsstapel kein auszuführender Code vorhanden ist, d. h. unser aktueller Hauptthread keinen synchronen Code hat, nimmt die Taskwarteschlange eine Mikrotask aus unserer Mikrotaskwarteschlange für asynchrone Tasks und legt sie zur Ausführung in unsere Taskwarteschlange . Anschließend legt sie ihre Rückruffunktion zur Ausführung erneut in den Ausführungsstapel . Wenn die Mikrotaskwarteschlange leer ist , wird die asynchrone Task aus der Makrotask genommen und der Taskwarteschlange hinzugefügt . Anschließend wird sie in den Ausführungsstapel geschoben, die Rückruffunktion ausgeführt und dann weiter nach synchronen und asynchronen Tasks in der Makrotask gesucht. Ein Zyklus schließt eine Ereignisschleife ab (Ereignisabfrage).

Beispiel in einer Browserumgebung:

Beispiel:

        konsole.log("1");
        setzeTimeout(() => {
            console.log("Zeitüberschreitung festlegen");
        }, 1);
        neues Versprechen((res, rej) => {
            console.log("Versprechen");
            res('VersprechenRes')
        }).dann(val => {
            konsole.log(Wert);
        })
        konsole.log("2");

analysieren:
Zuerst findet der Ausführungsstapel die erste Zeile von Synchroncode und wirft ihn direkt in den Ausführungsstapel, gefolgt von dem Timer -SetTimeout, der eine asynchrone Aufgabe ist. Iseres ') In der asynchronen Warteschlange wird der Synchron -Code erneut aufgenommen, und alle synchronen Codes des aktuellen Hauptfadens wurden ausgeführt. Die zurückgegebene Rückruffunktion in den Ausführungsstapel für Ausführung, drucken Versprechen, und dann wird der Mikrotask ausgeführt.

Knotenumgebungs-Ereignisschleife

Im Knoten ist die Ereignisschleife hauptsächlich in sechs Phasen unterteilt:

Externe Dateneingabe –> Polling-Phase –> Prüfphase –> Shutdown-Event-Callback-Phase –> Timer-Phase –> I/O-Callback-Phase –> Leerlaufphase –> Polling-Phase… Schleife starten

Sechs Etappen

Das Bild stammt aus dem Internet

Bildbeschreibung hier einfügen

  • Timerphase: wird verwendet, um den Rückruf des Timers (setTimeout, setInterval) auszuführen;
  • I/O-Callbackphase: Verarbeitet einige I/O-Callbacks, die im vorherigen Zyklus nicht ausgeführt wurden.
  • Leerlauf, Vorbereitungsphase: wird nur intern vom Knoten verwendet, wir brauchen sie nicht;
  • Pollphase: Neue I/O-Zeit abfragen. Unter entsprechenden Voraussetzungen wird der Knoten hier blockieren.
  • Prüfphase: Führen Sie den Rückruf von setImmediate() aus.
  • Phase „Callbacks schließen“: Führen Sie den Callback zum Schließen des Sockets aus

Hauptetappen :
Timer:
Die Timerphase führt setTimeout- und setInterval-Rückrufe aus und wird von der Polling-Phase gesteuert.
Ebenso ist die vom Timer im Knoten angegebene Zeit nicht die exakte Zeit, sie kann nur so schnell wie möglich ausgeführt werden.
Umfrage:
Während der Umfragephase führt das System zwei Dinge aus:
1. Kehren Sie zur Timer-Phase zurück, um den Rückruf auszuführen
2. Führen Sie einen I/O-Callback aus. Wenn beim Eintritt in diese Phase kein Timer eingestellt ist, passieren die folgenden beiden Dinge

Wenn die Polling-Warteschlange nicht leer ist, durchläuft sie die Rückrufwarteschlange und führt sie synchron aus, bis die Warteschlange leer ist oder das Systemlimit erreicht ist. Wenn die Polling-Warteschlange leer ist, passieren zwei Dinge
1. Wenn ein setImmediate-Rückruf ausgeführt werden muss, wird die Abfragephase angehalten und die Prüfphase aufgerufen, um den Rückruf auszuführen
2. Wenn kein setImmediate-Rückruf ausgeführt werden muss, wartet es, bis der Rückruf zur Warteschlange hinzugefügt wird, und führt den Rückruf sofort aus. Es gibt auch eine Timeout-Einstellung, um Wartezeiten zu vermeiden. Wenn ein Timer eingestellt ist und die Polling-Warteschlange leer ist, wird natürlich ermittelt, ob der Timer abgelaufen ist. Wenn ja, kehrt es zur Timer-Phase zurück, um den Rückruf auszuführen.

Prüfphase
Der Rückruf von setImmediate() wird der Prüfwarteschlange hinzugefügt. Aus dem Phasendiagramm der Ereignisschleife können wir erkennen, dass die Ausführungsreihenfolge der Prüfphase nach der Abfragephase liegt. Beim Eintritt in die Prüfphase prüft die Abfrage, ob eine vorhanden ist, und geht dann zur Prüfphase über. Wenn nicht, geht es direkt zur Timerphase über.

(1) setTimeout und setImmediate

Die beiden sind sehr ähnlich. Der Hauptunterschied besteht im Zeitpunkt des Anrufs.

setImmediate ist so konzipiert, dass es ausgeführt wird, wenn die Abfragephase, d. h. die Prüfphase, abgeschlossen ist, und es wird nur in der Prüfphase ausgeführt.
setTimeout ist so konzipiert, dass es ausgeführt wird, wenn die Abfragephase inaktiv ist und die festgelegte Zeit erreicht ist. Es wird jedoch in der Timerphase ausgeführt, was bedeutet, dass der aktuelle Thread keine anderen ausführbaren Synchronisierungsaufgaben hat und der Timer in der Timerphase ausgeführt wird.

Der Zeitpunkt dieser beiden Ausführungen kann entweder vor oder nach den folgenden Ereignissen liegen:
Beispiel 1:

// //Makroaufgabe in asynchroner Aufgabe setTimeout(() => {
    Konsole.log('===setTimeout===');
},0);
setImmediate(() => {
    Konsole.log('===setImmediate===')
})

Bildbeschreibung hier einfügen

Die Ergebnisse wiederholter Ausführungen werden unterschiedlich sein und es entsteht ein Gefühl der Zufälligkeit. Der Grund dafür hängt hauptsächlich mit dem Implementierungscode von setTimeout zusammen. Wenn wir den Zeitparameter nicht übergeben oder ihn auf 0 setzen, nimmt nodejs den Wert 1 an, also 1 ms (der Wert auf der Browserseite kann größer sein und verschiedene Browser sind auch unterschiedlich). Wenn die CPU des Computers stark genug ist, um die Timerphase innerhalb von 1 ms auszuführen, wird der Rückruf daher nicht ausgeführt, da die Zeitverzögerung die Anforderungen nicht erfüllt. Daher kann mit der Ausführung nur bis zur zweiten Runde gewartet werden. Daher wird setInterval zuerst ausgeführt.
Das oben beschriebene Zufallsphänomen kann durch geringfügige Unterschiede in der Zeit verursacht werden, die die CPU benötigt, um dieselbe Aufgabe mehrmals auszuführen, und kann im Bereich von 1 ms schwanken.
Wenn setTimeout 0 ist, wird es im Allgemeinen vor setImmediate ausgeführt

Beispiel 2:
Wenn der von uns übergebene Wert größer als die Rückrufzeit der Timer-Ausführung ist, wird der Timer direkt in der nächsten Ereignisschleife ausgeführt.

setzeTimeout(() => {
    Konsole.log('===setTimeout===');
},10);
setImmediate(() => {
    Konsole.log('===setImmediate===')
})

Bildbeschreibung hier einfügen

Beispiel 3:
Wenn wir den obigen Code in einen I/O einfügen, wird immer zuerst eine Prüfung und dann der Timer durchgeführt:

const fs = erfordern('fs');

fs.readFile("./any.js", (Daten) => {
    setzeTimeout(() => {
        Konsole.log('===setTimeout===');
    },10);
    setImmediate(() => {
        Konsole.log('===setImmediate===')
    })
});

Bildbeschreibung hier einfügen

In der ersten Runde der Schleife wird die Datei gelesen. Im Rückruf wird die Prüfphase aufgerufen und setImmediate ausgeführt. Anschließend wird der Timer in der Timerphase ausgeführt.
Wenn setimmediate und settimeout in derselben E/A-Schleife aufgerufen werden, wird setImmediate immer zuerst aufgerufen.

(2) Prozess.nextTick

Diese Funktion ist eigentlich unabhängig von der Ereignisschleife. Sie hat ihre eigene Warteschlange. Wenn jede Phase abgeschlossen ist und eine nextTick-Warteschlange vorhanden ist, werden alle Rückruffunktionen in der Warteschlange gelöscht und vor anderen Mikrotasks ausgeführt.

Beispiel 1:

setzeTimeout(() => {
 Konsole.log('Timer1')
 Versprechen.auflösen().dann(Funktion() {
   console.log('Versprechen1')
 })
}, 0)
Prozess.nextTick(() => {
 console.log('nächsterTick')
 Prozess.nextTick(() => {
   console.log('nächsterTick')
   Prozess.nextTick(() => {
     console.log('nächsterTick')
     Prozess.nextTick(() => {
       console.log('nächsterTick')
     })
   })
 })
})
// nächsterTick=>nächsterTick=>nächsterTick=>nächsterTick=>timer1=>promise1

Beispiel 2:

const fs = erfordern('fs');

fs.readFile("./any.js", (Daten) => {
    Prozess.nextTick(()=>console.log('Prozess===2'))
    setzeTimeout(() => {
        Konsole.log('===setTimeout===');
    },10);
    setImmediate(() => {
        Konsole.log('===setImmediate===')
    })
});
Prozess.nextTick(()=>console.log('Prozess===1'))

Bildbeschreibung hier einfügen

Praxisbeispiele

asynchrone Funktion async1() {
    konsole.log('2')
    //Warten auf die Beendigung von await, führen die Ausführung jedoch nicht weiter aus, da der folgende Microtask await async2() aufgerufen wird.
    konsole.log('9')
  }
   
   Funktion async2() {
    konsole.log('3')
  }
   
  konsole.log('1')
   
  setzeTimeout(Funktion () {
    konsole.log('11')
  }, 0)
   
  setzeTimeout(Funktion () {
    konsole.log('13')
  }, 300)
   
  setImmediate(() => console.log('12'));
   
  Prozess.nextTick(() => console.log('7'));
   
  async1();
   
  Prozess.nextTick(() => console.log('8'));
   
  neues Versprechen (Funktion (Auflösung) {
    konsole.log('4')
    lösen();
    konsole.log('5')
  }).dann(Funktion () {
    konsole.log('10')
  })
   
  konsole.log('6')

analysieren:
Die obige Reihenfolge ist die Reihenfolge der Seriennummern.
Erster Druck 1:
Vorne stehen zwei Funktionsdeklarationen, die alle direkt 1 drucken, diese Zeile ist synchroner Code.
Druck 2:
Nach dem Drucken von 1 sind alle Codes asynchron. Sie werden der asynchronen Aufgabenwarteschlange hinzugefügt und direkt in der Funktion async1 aufgerufen. In dieser Funktion wird 2 gedruckt.
Druck 3:
Die Funktion async1 ist eine asynchrone Wartefunktion, also auch eine getarnte synchrone Operation, die auf die Ausführung der Funktion async2 wartet. Nachdem async2 ausgeführt wurde, wird 9 nicht direkt gedruckt, da await die then-Operation eines Promises akzeptiert, sodass die zu einem Promise gehörende Callback-Operation eine Mikrotask ist und der Mikrotask-Warteschlange hinzugefügt wird.
Druck 4:
process.nextTick ist eine Mikrotask, daher wird es mit der Ausführung des Versprechens fortfahren und 4 ausgeben;
Druck 5:
Der Rückruf von resolve() wird nicht sofort ausgeführt und gehört zur Mikrotask. Er wird der Mikrotask-Warteschlange hinzugefügt, daher wird 5 ausgegeben;
Druck 6:
Der letzte Synchronisierungscode des Hauptthreads gibt 6 aus;
Ausdrucke 7 und 8:
process.nextTick hat eine höhere Priorität als andere Timer, daher wird die Rückruffunktion direkt ausgeführt, um 7 und 8 auszudrucken;
Druck 9, 10:
Zu diesem Zeitpunkt müssen die Mikrotasks in der Mikrotask-Warteschlange ausgeführt werden. Derzeit gibt es zwei 9 und 10. In der Reihenfolge wird zuerst 9 und dann 10 gedruckt.
Druck 11, 12:
setTimeout beträgt 0 Sekunden, was früher ist als setImmediate. In der Reihenfolge der Ausführung wird zuerst 11 und später 12 gedruckt.
Druck 13:
Die Funktion mit einem setTimeout von 300 ms gibt 13 aus;

Beispiel:

asynchrone Funktion async1() {
    konsole.log('2')
    //Warten auf die Beendigung von await, führen aber die Ausführung nicht weiter aus, da der folgende Microtask await async2() aufgerufen wird.
    konsole.log('9')
  }
   
   Funktion async2() {
    konsole.log('3')
  }
   
  konsole.log('1')
   
  setzeTimeout(Funktion () {
    konsole.log('11')
    setzeTimeout(() => {
        konsole.log('11-1');
    },100);
    setImmediate(() => {
        konsole.log('11-2')
    })
  }, 0)
   
  setzeTimeout(Funktion () {
    konsole.log('13')
    setzeTimeout(() => {
        konsole.log('15');
    },10);
    setImmediate(() => {
        konsole.log('14')
    })
  }, 300)
  setImmediate(() => console.log('12'));
  Prozess.nextTick(() => console.log('7'));
  async1();
   
  Prozess.nextTick(() => console.log('8'));
   
  neues Versprechen (Funktion (Auflösung) {
    konsole.log('4')
    lösen();
    konsole.log('5')
  }).dann(Funktion () {
    konsole.log('10')
  })
   
  konsole.log('6')

Zusammenfassen:

Dies ist das Ende dieses Artikels über die Reihenfolge der Ereignisausführung in der Knotenereignisschleife. Weitere Informationen zur Reihenfolge der Knotenereignisausführung finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Referenz: https://www.cnblogs.com/everlose/p/12846375.html

Das könnte Sie auch interessieren:
  • Erkundung des Zeitpunkts und der Reihenfolge der Ondata-Auslösung von Node.js-Streams

<<:  Beispiel zum Anzeigen und Ändern der MySQL-Transaktionsisolationsebene

>>:  Beispiel zum Erstellen eines öffentlichen Harbor-Repository mit Docker

Artikel empfehlen

7 nützliche neue TypeScript-Funktionen

Inhaltsverzeichnis 1. Optionale Verkettung 2. Nul...

Zehn wichtige Fragen zum Erlernen der Grundlagen von Javascript

Inhaltsverzeichnis 1. Was ist Javascript? 2. Was ...

Vue erzielt einen nahtlosen Karusselleffekt

In diesem Artikel wird der spezifische Code von V...

Lösen Sie das Problem der inkonsistenten MySQL-Speicherzeit

Nachdem die Systemzeit mit Java ermittelt und in ...

Detaillierte Analyse von Javascript-Datenproxys und Ereignissen

Inhaltsverzeichnis Datenbroker und Events Überprü...

RGB-Farbtabellensammlung

RGB-Farbtabelle Farbe Englischer Name RGB 16 Farb...

Spezifische Verwendung von Vues neuem Spielzeug VueUse

Inhaltsverzeichnis Vorwort Was ist VueUse Einfach...

Implementierung eines Element-Eingabefelds, das automatisch den Fokus erhält

Beim Erstellen eines Formulars in einem aktuellen...

Zusammenfassung von 28 gängigen JavaScript-String-Methoden und Verwendungstipps

Inhaltsverzeichnis Vorwort 1. Ermitteln Sie die L...