Dieser Artikel ist ein persönlicher Einblick in die tatsächliche Entwicklung und das Lernen von Nodejs. Er ist nun für zukünftige Referenzzwecke zusammengestellt. Es wäre mir eine Ehre, wenn er Sie inspirieren könnte.
E/A : Eingabe/Ausgabe, die Eingabe und Ausgabe eines Systems.
Unter einem System versteht man ein Individuum, beispielsweise eine Person. Wenn man spricht, ist es der Output, und wenn man zuhört, ist es der Input.
Der Unterschied zwischen blockierender und nicht blockierender E/A besteht darin, ob das System im Zeitraum von der Eingabe bis zur Ausgabe andere Eingaben empfangen kann .
Hier sind zwei Beispiele, um zu veranschaulichen, was blockierende E/A und nicht blockierende E/A sind:
1. Kochen
Zunächst müssen wir den Umfang eines Systems bestimmen. In diesem Beispiel werden die Kantinentante und der Kellner im Restaurant als System betrachtet .
Wenn Sie dann zwischen der Bestellung und dem Servieren von Speisen die Bestellungen anderer Personen annehmen können, können Sie feststellen, ob es sich um blockierende oder nicht blockierende E/A handelt.
Was die Cafeteria-Tante betrifft, kann sie beim Bestellen nicht für andere Schüler bestellen. Erst nachdem der Student die Bestellung abgeschlossen und das Geschirr serviert hat, kann sie die Bestellung des nächsten Schülers annehmen, sodass die Cafeteria-Tante die E/A blockiert.
Ein Restaurantkellner kann den nächsten Gast nach der Bestellung bedienen, bevor der Gast das Gericht serviert, sodass der Kellner über nicht blockierende E/A verfügt.
2. Hausarbeit erledigen
Beim Wäschewaschen müssen Sie nicht auf die Waschmaschine warten. Sie können zu diesem Zeitpunkt den Boden fegen und den Schreibtisch aufräumen Insgesamt 25 Minuten.
Beim Wäschewaschen handelt es sich eigentlich um einen nicht blockierenden Vorgang. Zwischen dem Einlegen der Wäsche in die Waschmaschine und dem Beenden des Waschvorgangs können Sie andere Dinge tun.
Der Grund, warum nicht blockierende E/A die Leistung verbessern kann, besteht darin, dass unnötiges Warten eingespart werden kann.
Der Schlüssel zum Verständnis nicht blockierender E/A liegt
Wie spiegelt sich die nicht blockierende E/A von NodeJS wider? Wie bereits erwähnt, besteht ein wichtiger Punkt beim Verständnis nicht blockierender E/A darin, zunächst eine Systemgrenze zu bestimmen. Die Systemgrenze des Knotens ist der Hauptthread .
Wenn das Architekturdiagramm unten nach Thread-Wartung unterteilt ist, ist die gepunktete Linie links der NodeJS-Thread und die gepunktete Linie rechts der C++-Thread.
Jetzt muss der NodeJS-Thread die Datenbank abfragen. Dies ist eine typische E/A-Operation. Er wartet nicht auf die Ergebnisse der E/A und verarbeitet weiterhin andere Operationen Threads für Berechnungen.
Warten Sie, bis das Ergebnis ausgegeben wird, und geben Sie es an den NodeJS-Thread zurück. Bevor Sie das Ergebnis erhalten, kann der NodeJS-Thread auch andere E/A-Vorgänge ausführen, sodass er nicht blockiert.
Der NodeJS-Thread entspricht dem linken Teil, der der Kellner ist, und der C++-Thread ist der Chef.
Daher wird die nicht blockierende E/A des Knotens durch den Aufruf von C++-Worker-Threads abgeschlossen.
Wie kann man also den NodeJS-Thread benachrichtigen, wenn der C++-Thread das Ergebnis erhält? Die Antwort ist ereignisgesteuert .
Blockieren : Der Prozess schläft während der E/A und wartet auf den Abschluss der E/A, bevor er mit dem nächsten Schritt fortfährt.
Nicht blockierend : Die Funktion kehrt während der E/A sofort zurück und der Prozess wartet nicht auf die E/A. O zum Vervollständigen.
Um das zurückgegebene Ergebnis zu erfahren, müssen Sie den Ereignistreiber verwenden.
Das sogenannte ereignisgesteuerte Ereignis kann als das gleiche verstanden werden wie das Front-End-Klickereignis. Ich schreibe zunächst ein Klickereignis, weiß jedoch nicht, wann es ausgelöst wird Führen Sie die ereignisgesteuerte Funktion aus.
Dieser Modus ist auch ein Beobachtermodus, das heißt, ich höre mir zuerst das Ereignis an und führe es dann aus, wenn es ausgelöst wird.
Wie implementiert man also Event Drive? Die Antwort ist asynchrone Programmierung .
Wie oben erwähnt, verfügt NodeJS über eine große Anzahl nicht blockierender E/A, daher müssen die Ergebnisse nicht blockierender E/A über Rückruffunktionen abgerufen werden. Diese Methode zur Verwendung von Rückruffunktionen ist asynchrone Programmierung . Beispielsweise erhält der folgende Code das Ergebnis über die Rückruffunktion:
glob(__dirname+'/**/*', (err, res) => { Ergebnis = res console.log('Ergebnis abrufen') })
Der erste Parameter der NodeJS-Rückruffunktion ist Fehler, und die nachfolgenden Parameter sind das Ergebnis . Warum das tun?
versuchen { interview(function () { console.log('smile') }) } Catch(err) { console.log('cry', äh) } Funktionsinterview(Rückruf) { setTimeout(() => { if(Math.random() < 0,1) { Rückruf('Erfolg') } anders { wirf einen neuen Fehler ('fail') } }, 500) }
Nach der Ausführung wurde es nicht abgefangen und der Fehler wurde global ausgegeben, was zum Absturz des gesamten NodeJS-Programms führte.
Es wird nicht von try Catch abgefangen, da setTimeout die Ereignisschleife erneut öffnet, wenn ein Aufrufstapel neu generiert wird, der zum Aufrufstapel der vorherigen Ereignisschleife gehört. Der Call-Stack-Kontext Alles ist anders. In diesem neuen Call-Stack gibt es keinen Try-Catch, daher wird der Fehler global ausgelöst und kann nicht abgefangen werden. Weitere Informationen finden Sie in diesem Artikel. Probleme beim Durchführen von Try-Catch in der asynchronen Warteschlange.
Was also tun? Übergeben Sie den Fehler als Parameter:
function interview(callback) { setTimeout(() => { if(Math.random() < 0,5) { Rückruf('Erfolg') } anders { Rückruf(neuer Fehler('fail')) } }, 500) } interview(function (res) { if (res Instanz des Fehlers) { console.log('cry') zurückkehren } console.log('smile') })
Dies ist jedoch problematischer und Sie müssen im Rückruf ein Urteil fällen, daher gibt es eine ausgereifte Regel. Der erste Parameter ist err. Wenn er nicht vorhanden ist, bedeutet dies, dass die Ausführung erfolgreich ist.
Funktionsinterview(Rückruf) { setTimeout(() => { if(Math.random() < 0,5) { Rückruf (null, 'Erfolg') } anders { Rückruf(neuer Fehler('fail')) } }, 500) } interview(function (res) { if (res) { zurückkehren } console.log('smile') })Die Callback-Schreibmethode von Nodejs für
führt nicht nur zum Rückrufbereich, sondern auch zum Problem der asynchronen Prozesssteuerung .
Die asynchrone Prozesssteuerung bezieht sich hauptsächlich auf die Handhabung der Parallelitätslogik, wenn Parallelität auftritt. Nehmen wir weiterhin das obige Beispiel: Wenn Ihr Kollege zwei Unternehmen interviewt, wird er erst dann von dem dritten Unternehmen interviewt, wenn er zwei Unternehmen erfolgreich interviewt hat. Wie schreibt man diese Logik? Sie müssen global eine Variablenanzahl hinzufügen:
var count = 0 interview((err) => { if (irre) { zurückkehren } count++ if (count >= 2) { // Verarbeitungslogik} }) interview((err) => { if (irre) { zurückkehren } count++ if (count >= 2) { // Verarbeitungslogik} })
So zu schreiben ist sehr mühsam und hässlich. Daher erschienen später die Schreibmethoden Promise und Async/Await.
, dass die aktuelle Ereignisschleife das Ergebnis nicht erhalten kann, die zukünftige Ereignisschleife Ihnen jedoch das Ergebnis liefern wird. Es ist dem sehr ähnlich, was ein Drecksack sagen würde.
Promise ist nicht nur ein Drecksack, sondern auch eine Zustandsmaschine:
const pro = new Promise((resolve, ablehn) => { setTimeout(() => { auflösen('2') }, 200) }) console.log(pro) // Drucken: Promise { <pending> }
Die Ausführung von then oder Catch gibt ein neues Versprechen zurück . Der endgültige Status des Versprechens wird durch die Ausführungsergebnisse der Rückruffunktionen von then und Catch bestimmt:
Funktion interview() { neues Versprechen zurückgeben((auflösen, ablehnen) => { setTimeout(() => { if (Math.random() > 0,5) { lösen('Erfolg') } anders { abweisen(neuer Fehler('fail')) } }) }) } var versprechen = interview() var versprechen1 = versprechen.then(() => { neues Versprechen zurückgeben((auflösen, ablehnen) => { setTimeout(() => { lösen('akzeptieren') }, 400) }) })
Der Status von Versprechen1 wird durch den Status von Versprechen als Gegenleistung bestimmt, d. h. der Status von Versprechen1 nach der Ausführung des Versprechens als Gegenleistung. Was sind die Vorteile davon? Dies löst das Problem der Rückrufhölle .
var versprechen = interview() .then(() => { Rückgespräch() }) .then(() => { Rückgespräch() }) .then(() => { Rückgespräch() }) .catch(e => { console.log(e) })
Wenn der Status des zurückgegebenen Versprechens dann abgelehnt wird, wird der erste Catch aufgerufen und der nachfolgende Catch wird nicht aufgerufen. Denken Sie daran: Abgelehnte Anrufe sind der erste Fang, gelöste Anrufe dann der erste.
Wenn Versprechen nur dazu dient, Höllenrückrufe zu lösen, ist es zu klein, um Versprechen zu unterschätzen. Die Hauptfunktion von Versprechen besteht darin, asynchrone Prozesssteuerungsprobleme zu lösen. Wenn Sie zwei Unternehmen gleichzeitig interviewen möchten:
Funktion interview() { neues Versprechen zurückgeben((auflösen, ablehnen) => { setTimeout(() => { if (Math.random() > 0,5) { lösen('Erfolg') } anders { abweisen(neuer Fehler('fail')) } }) }) } versprechen .all([interview(), interview()]) .then(() => { console.log('smile') }) // Wenn ein Unternehmen abgelehnt hat, fangen Sie es ab .catch(() => { console.log('cry') })
Was genau ist sync/await:
console.log(async function() { zurück 4 }) console.log(function() { neues Versprechen zurückgeben((auflösen, ablehnen) => { lösen(4) }) })
Das gedruckte Ergebnis ist das gleiche, das heißt, async/await ist nur syntaktischer Zucker für Versprechen.
Wir wissen, dass Try Catch Fehler basierend auf dem Aufrufstapel erfasst und nur Fehler oberhalb des Aufrufstapels erfassen kann. Wenn Sie jedoch „await“ verwenden, können Sie Fehler in allen Funktionen im Aufrufstapel abfangen. Auch wenn der Fehler in der Aufrufliste einer anderen Ereignisschleife, z. B. setTimeout, ausgegeben wird.
Nach der Transformation des Interviewcodes können Sie sehen, dass der Code erheblich optimiert wurde.
versuchen { warte auf Vorstellungsgespräch(1) warte auf Vorstellungsgespräch(2) warte auf Vorstellungsgespräch(2) } Catch(e => { console.log(e) })
Was ist, wenn es sich um eine parallele Aufgabe handelt?
Warten Sie auf Promise.all([interview(1), interview(2)])
Aufgrund der nicht blockierenden E/A von nodejs ist es notwendig, ereignisgesteuerte Methoden zu verwenden, um E/A-Ergebnisse zu erzielen Um Ergebnisse zu erhalten, müssen Sie mit gesteuerten Methoden asynchrone Programmierung verwenden, z. B. Rückruffunktionen. Wie führt man diese Rückruffunktionen aus, um die Ergebnisse zu erhalten? Dann müssen Sie eine Ereignisschleife verwenden.
Die Ereignisschleife ist die wichtigste Grundlage für die Realisierung der nicht blockierenden E/A-Funktion von nodejs. Nicht blockierende E/A und Ereignisschleife sind Funktionen, die von der C++-Bibliothek libuv
bereitgestellt werden.
Codedemonstration:
const eventloop = { Warteschlange: [], Schleife() { while(this.queue.length) { const callback = this.queue.shift() Rückruf() } setTimeout(this.loop.bind(this), 50) }, add(Rückruf) { this.queue.push(Rückruf) } } eventloop.loop() setTimeout(() => { eventloop.add(() => { console.log('1') }) }, 500) setTimeout(() => { eventloop.add(() => { console.log('2') }) }, 800)
setTimeout(this.loop.bind(this), 50)
stellt sicher, dass nach 50 ms geprüft wird, ob sich ein Rückruf in der Warteschlange befindet, und wenn ja, diesen ausführen. Dies bildet eine Ereignisschleife.
Natürlich sind die tatsächlichen Ereignisse viel komplizierter und es gibt mehr als eine Warteschlange. Beispielsweise gibt es eine Dateioperationswarteschlange und eine Zeitwarteschlange.
const eventloop = { Warteschlange: [], fsQueue: [], timerQueue: [], Schleife() { while(this.queue.length) { const callback = this.queue.shift() Rückruf() } this.fsQueue.forEach(callback => { wenn (erledigt) { Rückruf() } }) setTimeout(this.loop.bind(this), 50) }, add(Rückruf) { this.queue.push(Rückruf) } }
Zunächst verstehen wir, was nicht blockierende E/A ist, das heißt, die Ausführung nachfolgender Aufgaben wird sofort übersprungen, wenn auf E/A gestoßen wird, und wir werden nicht auf das Ergebnis der E/A warten. Wenn die E/A verarbeitet wird, wird die von uns registrierte Ereignisverarbeitungsfunktion aufgerufen, die als ereignisgesteuert bezeichnet wird. Asynchrone Programmierung ist erforderlich, um den Ereignisantrieb zu implementieren. Sie reicht von der Callback-Funktion über Promise bis hin zu Async/Await (unter Verwendung der synchronen Methode zum Schreiben asynchroner Logik).