Node.js Event Loop

Die Ereignisschleife ist der Kernmechanismus, mit dem Node.js nicht blockierende E/A-Operationen verarbeitet und es einem einzelnen Thread ermöglicht, mehrere gleichzeitige Anfragen effizient zu bearbeiten.

Node.js ist eine einfädigige JavaScript-Laufzeitumgebung, die die Ereignisschleife verwendet, um asynchrone Operationen wie Dateilesungen, Netzwerkanfragen und Datenbankabfragen zu verarbeiten.

Die Ereignisschleife ermöglicht es Node.js, Code nicht blockierend auszuführen, mehrere Verbindungen zu verarbeiten und asynchrone E/A-Operationen durchzuführen.

Die Event-Loop ermöglicht es Node.js, eine große Anzahl gleichzeitiger I/O-Operationen zu verarbeiten, ohne Thread-Blockierung zu verursachen; dies ist der Schlüssel dafür, dass Node.js gleichzeitige Anfragen effizient verarbeitet.

Phasen der Event-Loop

Die Event-Loop ist in mehrere Phasen unterteilt, die jeweils spezifische Aufgaben übernehmen. Die wichtigsten Phasen sind wie folgt:

  • Timers: FührtsetTimeout()undsetInterval()Callbacks aus.
  • I/O Callbacks: Verarbeitet einige verzögerte I/O-Callbacks.
  • Idle, prepare: Wird intern verwendet, nicht üblich.
  • Poll: Ruft neue I/O-Ereignisse ab und führt I/O-bezogene Callbacks aus.
  • Check: AusführensetImmediate()Callbacks aus.
  • Close Callbacks: Behandelt Close-Callbacks, wie z. B.socket.on('close', ...)。

Ablauf der Event-Loop

  • Aufgaben gelangen in die Event-Loop-Warteschlange。
  • Die Event-Loop verarbeitet die Phasen in ihrer Reihenfolge, und jede Phase hat ihre eigene Callback-Warteschlange.
  • Die Event-Loop wird sich in derpollPhase, die auf neue Ereignisse wartet; wenn keine Ereignisse eintreffen, prüft sie Callbacks in anderen Phasen.
  • WennsetImmediate()undsetTimeout()beide vorhanden sind,setImmediate()Incheckdie Phase zuerst ausgeführt wird, währendsetTimeout()Intimersdie Phase ausgeführt wird.

Beispiel

setTimeout(() => {
  console.log('Timeout callback');
}, 0);

setImmediate(() => {
  console.log('Immediate callback');
});

console.log('Main thread execution');

Ausgabereihenfolge:

  • Main thread executionWird zuerst ausgegeben.
  • setImmediate()undsetTimeout()Die Ausführungsreihenfolge hängt vom aktuellen Zustand der Event-Loop ab; im AllgemeinensetImmediate()wird zuerst ausgeführt.

Makrotasks und Mikrotasks

  • Makrotasks:setTimeout、setInterval、setImmediate, I/O-Operationen usw.
  • Mikrotasks:process.nextTick、Promise.then。

Ausführungsreihenfolge:Mikrotasks haben eine höhere Priorität als Makrotasks und werden unmittelbar ausgeführt, nachdem die Callbacks der aktuellen Phase beendet sind.

Beispiel

setTimeout(() => {
  console.log('Timeout callback');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise callback');
});

console.log('Main thread execution');

Ergebnis der Ausführung:

Main thread execution
Promise callback
Timeout callback

process.nextTick()

process.nextTick() führt Mikrotasks aus, nachdem die aktuelle Operation beendet ist und bevor die nächste Phase beginnt, mit höherer Priorität als Promise.

Beispiel

process.nextTick(() => {
  console.log('Next tick callback');
});

console.log('Main thread execution');

Ausgabe:

Main thread execution
Next tick callback

Ereignisgesteuertes Programm

In Node.js wird ereignisgesteuerte Programmierung hauptsächlich durch die EventEmitter-Klasse implementiert.

EventEmitter ist eine integrierte Klasse im events-Modul. Durch Erweitern von EventEmitter können Sie Ihren eigenen Event-Emitter erstellen und Ereignisse registrieren und auslösen.

Durch diesen Mechanismus kann Node.js asynchrone Aufgaben effizient verarbeiten und sogar in einer Single-Threaded-Umgebung eine nebenläufige Verarbeitung erreichen.

Der gesamte ereignisgesteuerte Ablauf wird genau auf diese Weise implementiert; er ist sehr prägnant. Er ähnelt etwas dem Observer-Muster: Ein Ereignis entspricht einem Subjekt (Subject), und alle für dieses Ereignis registrierten Handler-Funktionen entsprechen Beobachtern (Observer).

Grundlegende Konzepte:

  • Ereignis: Eine Aktion oder Zustandsänderung, die in einem Programm auftritt, wie das Abschließen eines Dateilesevorgangs oder das Eintreffen einer HTTP-Anfrage.
  • EventEmitter:EventEmitterist ein integriertes Modul von Node.js, das zum Emittieren und Abhören von Ereignissen verwendet wird.
  • Ereignis-Handler: Eine Callback-Funktion, die einem Ereignis zugeordnet ist und aufgerufen wird, wenn das Ereignis eintritt.

Ereignisgesteuerter Ablauf:

  • Ereignis registrieren: Im Programm, durchEventEmittereine Instanz registriert Ereignisse und entsprechende Handler.
  • Ereignis auslösen: Wenn das angegebene Ereignis eintritt,EventEmitteres löst das Ereignis aus.
  • Ereignis behandeln: Die Ereignisschleife plant die entsprechende Callback-Funktion ein, um die Aufgabe auszuführen.

Node.js verfügt über mehrere integrierte Ereignisse. Wir können Ereignisse binden und auf Ereignisse hören, indem wir das events-Modul importieren und die Klasse EventEmitter instanziieren, wie im folgenden Beispiel:

// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();

Das folgende Programm bindet Ereignishandler:

// 绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);

Wir können Ereignisse über das Programm auslösen:

// 触发事件
eventEmitter.emit('eventName');

Beispiel

Erstellen Sie eine Datei hello.js, der Code lautet wie folgt:

Beispiel 1

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

// Ereignishandler registrieren
myEmitter.on('greet', () => {
  console.log('Hello, world!');
});

// Ereignis auslösen
myEmitter.emit('greet');

Ausgabe:

Hello, world!

Erstellen Sie eine main.js-Datei, der Code ist wie folgt:

Beispiel 2

//Importieren Sie das events-Modul var events = require('events'); //Erstellen Sie ein eventEmitter-Objekt var eventEmitter = new events.EventEmitter(); //Erstellen Sie einen Event-Handler var connectHandler = function connected() { console.log('Verbindung erfolgreich.'); //Lösen Sie das data_received-Ereignis aus eventEmitter.emit('data_received'); } //Binden Sie den Event-Handler für das connection-Ereignis eventEmitter.on('connection', connectHandler); //Verwenden Sie eine anonyme Funktion, um das data_received-Ereignis zu binden eventEmitter.on('data_received', function(){ console.log('Daten erfolgreich empfangen.'); }); //Lösen Sie das connection-Ereignis aus eventEmitter.emit('connection'); console.log("Programmausführung abgeschlossen.");

Als Nächstes führen wir den obigen Code aus:

$ node main.js
连接成功。
数据接收成功。
程序执行完毕。

Wie funktioniert eine Node-Anwendung?

In Node-Anwendungen nehmen Funktionen, die asynchrone Operationen ausführen, eine Callback-Funktion als letzten Parameter entgegen, und die Callback-Funktion erhält ein Fehlerobjekt als ersten Parameter.

Als Nächstes sehen wir uns das vorherige Beispiel noch einmal an. Erstellen Sie eine Datei input.txt mit folgendem Inhalt:

Example官网地址:www.example.com

Erstellen Sie eine Datei main.js mit folgendem Code:

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
   if (err){
      console.log(err.stack);
      return;
   }
   console.log(data.toString());
});
console.log("程序执行完毕");

Im obigen Programm ist fs.readFile() eine asynchrone Funktion, die zum Lesen von Dateien verwendet wird. Wenn während des Dateileseprozesses ein Fehler auftritt, gibt das Fehlerobjekt err die Fehlermeldung aus.

Wenn kein Fehler auftritt, überspringt readFile die Ausgabe des err-Objekts, und der Dateiinhalt wird über die Callback-Funktion ausgegeben.

Führen Sie den obigen Code aus; das Ausführungsergebnis lautet wie folgt:

程序执行完毕
Example官网地址:www.example.com

Als Nächstes löschen wir die Datei input.txt, und das Ausführungsergebnis ist wie folgt:

程序执行完毕
Error: ENOENT, open 'input.txt'

Da die Datei input.txt nicht existiert, wird eine Fehlermeldung ausgegeben.

Weitere Erweiterungen