‫Kursleiter: Nehmen wir uns nun eine Minute Zeit, um mehr
‫über die asynchrone Natur von Node. js,
‫das absolut grundlegende Themen wie
‫synchronen, asynchronen, blockierenden und nicht
‫blockierenden Code umfasst.
‫Und all dies wird wirklich wichtig
‫sein, um alles zu verstehen, was in diesem
‫Abschnitt auftaucht.
‫Dieser Code, den wir in der letzten Vorlesung geschrieben
‫haben, um eine Datei zu lesen und ihren Inhalt dann in
‫einer Variablen zu speichern, war also
‫so genannte synchron, was einfach
‫bedeutet, dass jede Anweisung im Grunde nacheinander abgearbeitet wird, Zeile per Linie.
‫In diesem
‫Beispiel wird zuerst das Dateisystemmodul benötigt, dann
‫wird die Datei gelesen und
‫dann protokollieren wir das Ergebnis in der Konsole.
‫Sie sehen also, dass jede
‫Codezeile im Grunde auf das Ergebnis der vorherigen Zeile wartet.
‫Dies kann insbesondere bei langsamen
‫Operationen zu einem Problem werden,
‫da jede Zeile die Ausführung des restlichen
‫Codes blockiert.
‫Daher sagen wir, dass
‫synchroner Code auch als Blockierungscode bezeichnet wird, da eine
‫bestimmte Operation nur ausgeführt werden kann,
‫nachdem die vorherige abgeschlossen ist.
‫Und wegen des Weges Node. js entwickelt wurde,
‫wird dies zu einem großen Problem, wie wir auf der
‫nächsten Folie im Detail sehen werden.
‫Die Lösung für dieses Problem in
‫Node besteht also darin, asynchronen, nicht blockierenden Code zu verwenden.
‫In asynchronem Code laden wir also schwere Arbeit hoch,
‫die im Wesentlichen im Hintergrund bearbeitet wird.
‫Und wenn diese Arbeit erledigt
‫ist, wird eine Callback-Funktion aufgerufen, die wir zuvor
‫registriert haben, um das Ergebnis zu verarbeiten.
‫Und während all dieser
‫Zeit kann der Rest des Codes immer noch
‫ausgeführt werden, ohne durch die schwere Aufgabe,
‫die jetzt im Hintergrund läuft, blockiert zu werden.
‫Das bedeutet also, dass wir effektiv in die
‫Zukunft verschieben oder reagieren
‫können, um den Code blockierungsfrei zu
‫machen, und das ist natürlich viel besser.
‫Macht Sinn?
‫In diesem Beispiel verwenden wir
‫also die asynchrone Funktion readFile, die eine Callback-Funktion akzeptiert.
‫Dies beginnt mit dem Lesen der
‫Datei im Hintergrund und geht dann sofort zur nächsten
‫Anweisung über, die die String-lesende Datei an die Konsole ausgibt.
‫Sie sehen also, wir blockieren die Hinrichtung hier nicht.
‫Wenn die Datei schließlich vollständig gelesen ist, wird
‫die Callback-Funktion aufgerufen,
‫und die gelesenen Daten werden
‫dann an die Konsole ausgegeben.
‫Das ist also ganz
‫anders als die synchrone Version, oder?
‫Nun stellt sich hier
‫die Frage, warum muss das eigentlich so sein?
‫Was ist das Problem beim Blockieren der Codeausführung in Node. js?
‫Oder mit anderen Worten, warum verwenden wir
‫Callback in Node. js?
‫Nun, lass es uns herausfinden.
‫Um diese Fragen zu verstehen, müssen
‫wir zunächst einmal verstehen, dass
‫ein Node. js-Prozess, in
‫dem unsere Anwendung ausgeführt wird,
‫gibt es nur einen einzigen Thread.
‫Und der Thread ist wie eine Reihe von Anweisungen, die
‫in der CPU des Computers ausgeführt werden.
‫Im Grunde ist der Thread also der Ort,
‫an dem unser Code im Prozessor einer Maschine tatsächlich ausgeführt wird.
‫Denken Sie also daran, Node. js ist im Grunde
‫Single-Threaded und daher gibt es für jede Anwendung nur einen Thread.
‫So ist Node. js wurde entwickelt.
‫Das bedeutet, dass alle Benutzer, die
‫auf Ihre Anwendung zugreifen, alle denselben Thread verwenden, also
‫im Grunde genommen auf denselben Thread zugreifen.
‫Bei jeder Interaktion mit der Anwendung wird der Code,
‫der für jeden Benutzer ausgeführt
‫wird, im selben Thread an derselben Stelle auf dem Computer
‫ausgeführt, auf dem die Anwendung ausgeführt wird.
‫Und das unabhängig davon, ob
‫Sie fünf Benutzer haben, wie in diesem Diagramm, oder 5.000 oder 5 Millionen.
‫Gut?
‫Dies bedeutet auch, dass, wenn ein Benutzer den
‫einzelnen Thread mit synchronem Code sperrt,
‫wie wir gerade
‫gesehen haben, alle anderen Benutzer warten
‫müssen, bis die Ausführung abgeschlossen ist.
‫Und das ist vielleicht kein großes
‫Problem, wenn Sie etwa fünf Benutzer haben,
‫aber es wird definitiv für Tausende oder sogar Millionen
‫von Benutzern gleichzeitig sein.
‫Stellen Sie sich also vor, ein Benutzer greift auf Ihre
‫Anwendung zu und in Ihrem Code wird eine riesige synchrone
‫Datei gelesen, deren Laden etwa eine Sekunde dauert.
‫Dies bedeutet natürlich, dass für diese eine
‫Sekunde alle anderen Benutzer warten
‫müssen, da die gesamte Ausführung für diese eine Sekunde gesperrt ist.
‫Wenn diese anderen Benutzer also einige einfache Aufgaben erledigen
‫möchten, wie sich bei Ihrer Anwendung
‫anmelden oder nur einige Daten anfordern, können sie dies nicht tun.
‫Sie müssen warten, bis die Datei vollständig gelesen wurde.
‫Erst dann können sie endlich die
‫einfacheren Aufgaben nacheinander ausführen.
‫Bitte beachten Sie, dass dies eine sehr vereinfachte Version dessen ist, was wirklich
‫hinter den Kulissen von Node passiert. js, weshalb wir
‫im nächsten Abschnitt auf all
‫dies zurückkommen und ein noch tieferes Verständnis
‫dafür bekommen, wie Node. js verarbeitet asynchronen Code unter der Haube.
‫Aber an dieser
‫Stelle reicht dies, um das Konzept zu verstehen.
‫Es ist besser, hier Schritt für Schritt
‫vorzugehen und es nicht
‫von Anfang an zu unübersichtlich zu machen, okay?
‫Wie auch immer, so würde
‫sich die Situation mit synchronem Blockierungscode abspielen,
‫was für Ihre Benutzer offensichtlich eine schreckliche Erfahrung ist.
‫Es ist also wirklich Ihre Aufgabe als Entwickler,
‫solche Situationen durch die Verwendung von
‫asynchronem Code zu vermeiden.
‫Für die gleiche Situation sollten wir natürlich die
‫asynchrone Dateilesefunktion verwenden, die, anstatt den einzelnen
‫Thread zu blockieren, die schwere
‫Arbeit im Hintergrund verrichtet, wo sie
‫im Grunde bleibt, bis sie die
‫Daten aus der Datei gelesen hat.
‫Natürlich registrieren wir dann auch eine
‫Callback-Funktion, die aufgerufen wird, sobald die Daten vorliegen.
‫Und in diesem Szenario können dann
‫alle anderen Benutzer ihre Aufgaben in einem
‫einzigen Thread nacheinander ausführen, während die Datei noch im
‫Hintergrund gelesen wird.
‫Sobald die Daten gelesen sind, wird natürlich
‫unsere Callback-Funktion aufgerufen, die
‫im Haupt-Single-Thread ausgeführt wird, um die gelesenen Daten zu verarbeiten.
‫Und das ist es.
‫Das ist ein Überblick darüber, wie Node. js verarbeitet
‫asynchrones Verhalten, um das nicht blockierende I/O-Modell
‫zu implementieren, über das wir in
‫der Einführungsvorlesung gesprochen haben, in Ordnung?
‫Und I/O steht einfach für Input-Output, was im
‫Grunde Dinge wie der Zugriff auf das Dateisystem
‫und die Bearbeitung von Netzwerkanfragen ist.
‫Dies ist eigentlich der ganze Grund, warum Node. js ist vollständig
‫auf Rückrufe ausgelegt, wie Sie im
‫Verlauf des Kurses sehen werden.
‫In anderen Programmiersprachen wie PHP funktioniert es ganz
‫anders, weil man im Grunde genommen für jeden
‫neuen Benutzer einen neuen Thread bekommt,
‫was ein ganz anderes Paradigma
‫ist und wirklich ganz anders funktioniert.
‫Aber der Schöpfer von Node. js hat festgestellt,
‫dass dieses Modell die beste Lösung zum Erstellen
‫hochperformanter und skalierbarer Webanwendungen ist.
‫Nun, als letzte Anmerkung hier, ist es wichtig zu wissen,
‫dass, wenn wir Rückrufe
‫in unserem Code verwenden, dies nicht automatisch asynchron macht, in Ordnung?
‫Das Übergeben von Funktionen an andere
‫Funktionen ist in
‫JavaScript also ziemlich üblich, aber das
‫macht sie natürlich nicht automatisch asynchron, okay?
‫Dies funktioniert nur für einige Funktionen in der Node-API, wie
‫die readFile-Funktion und viele,
‫viele mehr, die die Leute in Zukunft erkunden werden.
‫Und jetzt zum
‫Schluss, da wir hier über asynchronen
‫Code sprechen, noch eine letzte Anmerkung zu Callback-Funktionen.
‫Dieses Callback-Modell, das wir gerade besprochen haben, bei
‫dem eine Funktion aufgerufen
‫wird, sobald die vorherige ihre Arbeit beendet
‫hat, kann schnell zu schwer lesbarem und nicht zu verwaltendem Code führen.
‫Nehmen Sie einfach dieses Beispiel, wo die zweite gelesene
‫Datei von der ersten abhängt,
‫dann die dritte gelesene Datei von der zweiten, und
‫schließlich möchten wir die endgültigen Daten verwenden, um
‫als Ergebnis eine Datei zu schreiben.
‫Das sieht ziemlich verwirrend aus, oder?
‫Ich meine, es wird gut
‫funktionieren, aber es ist nur schwer
‫zu argumentieren und das nur mit vier Ebenen.
‫Stellen Sie sich vor, Sie hätten 10 oder
‫20 Level, was eigentlich nicht ungewöhnlich ist.
‫Jedenfalls nennen wir das die Callback-Hölle.
‫Es ist ein so häufiges
‫Problem, dass es bereits einen eigenen Namen hat.
‫Und fällt Ihnen diese Dreiecksform hier auf?
‫Das ist ein sehr klares Zeichen dafür, dass Sie sich in der Callback-Hölle befinden.
‫Wie entkommen wir nun tatsächlich der Callback-Hölle?
‫Nun, wir können fortschrittlichere Tools für den
‫Umgang mit asynchronem Code verwenden,
‫wie ES6-Versprechen oder noch besser ES8 async/await.
‫Nun, das Modell, über das wir gerade gesprochen haben,
‫wird immer noch dasselbe sein.
‫Wir haben einfach elegantere Möglichkeiten,
‫mit dem Code selbst umzugehen und ihn zu schreiben.
‫Und es gibt einen ganzen
‫optionalen Abschnitt davon später im Kurs über Versprechen und auch async/await,
‫falls Sie damit nicht vertraut sind.
‫Aber vorerst werden wir weiterhin
‫Rückrufe verwenden, da Node ursprünglich verwendet
‫und entwickelt wurde.
‫Und jetzt, nachdem dies
‫gesagt wurde, lassen Sie uns dieses asynchrone Modell zum
‫ersten Mal in der Praxis verwenden.