Die "Event -Schleife" des Knotens ist der Kern seiner Fähigkeit, große Parallelität und hohen Durchsatz zu bewältigen. Dies ist der magischste Ort. Nach diesem zufolge kann Node.js im Grunde genommen als "ein Threading" verstanden werden und ermöglicht auch, dass willkürliche Operationen im Hintergrund verarbeitet werden. In diesem Artikel wird veranschaulicht, wie Eventschleifen funktionieren und Sie auch seine Magie fühlen können.
Ereignisgesteuerte Programmierung
Um Ereignisschleifen zu verstehen, müssen wir zunächst die Programmierung von Event Drive verstehen. Es erschien 1960. Heute wird die Ereignis-gesteuerte Programmierung in der UI-Programmierung häufig verwendet. Eine der Hauptanwendungen von JavaScript besteht darin, mit dem DOM zu interagieren. Daher ist es natürlich, eine ereignisbasierte API zu verwenden.
Einfach definieren: ereignisgesteuerte Programmiersteuerung steuert den Anwendungsprozess durch Änderungen in Ereignissen oder Zuständen. Es wird im Allgemeinen durch Ereignisüberwachung implementiert. Sobald das Ereignis erkannt wurde (d. H. Der Zustand ändert sich), wird die entsprechende Rückruffunktion aufgerufen. Klingt bekannt? Tatsächlich ist dies das grundlegende Arbeitsprinzip der Ereignisschleife von Node.js.
Wenn Sie mit der clientseitigen JavaScript-Entwicklung vertraut sind, denken Sie an diese .on*() -Methoden wie Element.onclick (), mit denen Sie mit DOM-Elementen kombiniert werden, um die Benutzerinteraktion zu übergeben. In diesem Arbeitsmodus können mehrere Ereignisse in einer einzigen Instanz ausgelöst werden. Node.js löst diesen Modus über EventEmitter (Event Generator) aus, wie z. B. in den Socket- und "HTTP" -Modulen auf der Serverseite. Ein oder mehrere Zustandsänderungen können aus einer einzigen Instanz ausgelöst werden.
Ein weiteres gemeinsames Muster ist das Ausdruck von Erfolg und Misserfolg. Im Allgemeinen gibt es jetzt zwei gemeinsame Implementierungsmethoden. Das erste ist, die "Fehlerausnahme" in den Rückruf zu übergeben, der im Allgemeinen als erster Parameter an die Rückruffunktion übergeben wird. Der zweite Typ besteht darin, den Versprechen -Design -Modus zu verwenden, und ES6 wurde hinzugefügt. HINWEIS* Versprechensmodus verwendet eine jQuery-ähnliche Funktionskettenschreibmethode, um eine tiefe Rückruffunktion zu vermeiden, z. B.:
Die Codekopie lautet wie folgt:
$ .getJson ('/GetUser'). Done (SuccessHandler) .Fail (failHandler)
Die Module "FS" (Dateisystem) verwenden hauptsächlich den Stil der Übergabe von Ausnahmen in den Rückruf. Technisch ausgelöst bestimmter Aufrufe, wie z. Eine solche API wird aus architektonischen Gründen und nicht aus technischen Einschränkungen ausgewählt.
Ein häufiges Missverständnis ist, dass Ereignisemitter auch bei Auslösen von Ereignissen von Natur aus asynchron sind, dies ist jedoch falsch. Hier ist ein einfacher Code -Snippet, um dies zu beweisen.
Die Codekopie lautet wie folgt:
Funktion myemitter () {
EventEmitter.call (this);
}
util.inherits (MyEmitter, Eventemitter);
MyEmitter.Prototype.dostuff = function dostuff () {
console.log ('vor')
Emitter.Emit ('Feuer')
console.log ('After')}
};
var me = new MyEmitter ();
me.on ('fire', function () {
console.log ('emit gefeuert');
});
me.dostuff ();
// Ausgabe:
// vor
// abgefeuert
// nach
Hinweis* Wenn Emitter.emit asynchron ist, sollte die Ausgabe sein
// vor
// nach
// abgefeuert
EventEmitter manifestiert sich oft asynchron, da es häufig verwendet wird, um Operationen zu benachrichtigen, die asynchron abgeschlossen werden müssen, aber die Eventemitter -API selbst ist vollständig synchron. Die Hörfunktion kann asynchron ausgeführt werden. Bitte beachten Sie jedoch, dass alle Hörfunktionen synchron in der Reihenfolge ausgeführt werden, in der sie hinzugefügt werden.
Mechanismusübersicht und Fadenpooling
Der Knoten selbst stützt sich auf mehrere Bibliotheken. Einer von ihnen ist Libuv, eine Bibliothek, die auf magische Weise asynchrone Ereigniswarteschlangen und -ausführungen behandelt.
Node verwendet so viele vorhandene Funktionen wie möglich, um den Kernel des Betriebssystems zu verwenden. Beispielsweise wird eine Antwortanforderung generiert, Verbindungen werden zur Verarbeitung weitergeleitet und an das System delegiert. Beispielsweise werden eingehende Verbindungen über das Betriebssystem in der Warteschlange gestellt, bis sie vom Knoten verarbeitet werden können.
Sie haben vielleicht gehört, dass der Knoten einen Thread -Pool hat, und Sie fragen sich vielleicht: "Wenn der Knoten Aufgaben in der Reihenfolge erledigt, warum brauchen Sie dann einen Thread -Pool?" Dies liegt daran, dass im Kernel nicht alle Aufgaben asynchron ausgeführt werden. In diesem Fall muss Node.js in der Lage sein, den Thread für einen bestimmten Zeitraum während des Betriebs zu sperren, damit er die Ereignisschleife weiter ausführen kann, ohne blockiert zu werden.
Das Folgende ist ein einfaches Beispieldiagramm, das seinen internen Betriebsmechanismus zeigt:
"
─kopf Timeser │
"
"
│ │ anhängige Rückrufe │
"
│ ┌wort
│ │ Umfrage │◄inger Verbindungen, │
│ └wort
"
──┤ setimmediat │
"
Es gibt einige Schwierigkeiten beim Verständnis des internen Betriebsmechanismus der Ereignisschleife:
Alle Rückrufe werden über Process.NextTick () vor dem Ende einer Phase der Ereignisschleife (z. B. ein Timer) und zur nächsten Phase voreingestellt. Dadurch wird der potenzielle rekursive Aufruf zum Verfahren verhindern.
"Ausstehende Rückrufe" ist ein Rückruf in der Callback -Warteschlange, der nicht durch einen anderen Ereignisschleifzyklus verarbeitet wird (z. B. an Fs.Write übergeben).
Ereignisemitter und Ereignisschleife
Durch das Erstellen von EventEmitter kann die Interaktion mit Ereignisschleifen vereinfacht werden. Es ist eine universelle Kapselung, die es Ihnen erleichtert, ereignisbasierte APIs zu erstellen. Wie die beiden interagieren, fühlen sich Entwickler oft verwirrt.
Das folgende Beispiel zeigt, dass das Vergessen, dass das Ereignis synchron ausgelöst wird, dazu führen kann, dass das Ereignis übersehen wird.
Die Codekopie lautet wie folgt:
// nach v0.10 benötigen ('Ereignisse'). EventEmitter ist nicht mehr benötigt
var EventEmitter = Request ('Ereignisse');
var util = require ('util');
Funktion mything () {
EventEmitter.call (this);
dofirstthing ();
this.emit ('Thing1');
}
util.inherits (Mything, Eventemitter);
var mt = new mything ();
Mt.on ('Thing1', Funktion aufThing1 () {
// Entschuldigung, dieser Vorfall wird niemals passieren
});
Das "Ding1" -Ver Ereignis oben wird niemals von Mything () gefangen, da Mything () instanziiert werden muss, bevor es auf Ereignisse zuhören kann. Hier ist eine einfache Problemumgehung, ohne zusätzliche Schließungen hinzufügen zu müssen:
Die Codekopie lautet wie folgt:
var EventEmitter = Request ('Ereignisse');
var util = require ('util');
Funktion mything () {
EventEmitter.call (this);
dofirstthing ();
setimmediat (emitthing1, this);
}
util.inherits (Mything, Eventemitter);
Funktion Emiting1 (selbst) {
self.emit ('Thing1');
}
var mt = new mything ();
Mt.on ('Thing1', Funktion aufThing1 () {
// Ausführen
});
Das folgende Schema kann auch funktionieren, aber einige Leistung geht verloren:
Die Codekopie lautet wie folgt:
Funktion mything () {
EventEmitter.call (this);
dofirstthing ();
// Die Verwendung von Funktion#Bind () verliert die Leistung
setImmediate (this.emit.bind (this, 'Thing1'));
}
util.inherits (Mything, Eventemitter);
Ein weiteres Problem ist, einen Fehler auszulösen (Ausnahme). Es ist bereits schwierig, das Problem in Ihrer Anwendung herauszufinden, aber ohne den Anrufstack (Hinweis *E.Stack) ist das Debuggen fast unmöglich. Der Anrufstack geht verloren, wenn der Fehler asynchron von der Remote angefordert wird. Es gibt zwei mögliche Lösungen: synchrones Auslösen oder Sicherstellen, dass der Fehler mit anderen wichtigen Informationen übergeben wird. Das folgende Beispiel zeigt diese beiden Lösungen:
Die Codekopie lautet wie folgt:
MyThing.Prototype.foo = Funktion foo () {
// Dieser Fehler wird asynchron ausgelöst
var er = dofirstthing ();
if (er) {
// Beim Auslösen müssen Sie einen neuen Fehler erstellen, der die Informationen vor Ort beibehält.
SetImmediate (emiterror, this, neuer Fehler ('schlechtes Zeug');
zurückkehren;
}
// Fehler auslösen und sofort verarbeiten (synchronisieren)
var er = doseseconding ();
if (er) {
this.emit ('error', 'mehr schlechtes Zeug');
zurückkehren;
}
}
Die Situation beurteilen. Wenn ein Fehler ausgelöst wird, ist es möglich, sofort verarbeitet zu werden. Alternativ kann es trivial sein und kann leicht behandelt oder später behandelt werden. Darüber hinaus ist es keine gute Idee, einen Fehler durch einen Konstruktor zu übertragen, da die konstruierte Objektinstanz wahrscheinlich unvollständig ist. Die Ausnahme ist der Fall, in dem der Fehler gerade jetzt direkt geworfen wurde.
Abschluss
Dieser Artikel untersucht kurz den internen Betriebsmechanismus und die technischen Details der Ereignisschleife. Alle werden sorgfältig berücksichtigt. In einem weiteren Artikel wird die Interaktion zwischen Ereignisschleifen und Systemkernel erörtert und die Magie von NodeJs demonstriert, die asynchron ausgeführt werden.