La parte central de un ordenador, o Sein, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama Prozessor. Die Shows, die wir bisher gesehen haben, sind Dinge, die das halten werden Prozessor bis sie ihre Arbeit beendet haben. Die Geschwindigkeit, mit der so etwas wie eine Schleife, die Zahlen manipuliert, ausgeführt werden kann, hängt stark von der Geschwindigkeit des Prozessors ab.
Viele Programme interagieren jedoch mit Dingen außerhalb des Prozessors. Beispielsweise können sie über ein Computernetzwerk kommunizieren oder Daten von der Festplatte anfordern, was viel langsamer ist als das Abrufen aus dem Speicher.
Wenn so etwas passiert, wäre es eine Schande, den Prozessor im Leerlauf zu lassen, da in der Zwischenzeit möglicherweise noch andere Arbeiten zu erledigen sind. Zum Teil wird dies von Ihrem Betriebssystem erledigt, das den Prozessor zwischen mehreren laufenden Programmen umschaltet. Dies hilft jedoch nicht, wenn ein einzelnes Programm ausgeführt werden soll, während auf eine Netzwerkanforderung gewartet wird.
Asynchronität
En un modelo de Programmierung sincrónica, las cosas suceden una a la vez. Cuando se llama a una función que realiza una acción de larga duración, sólo vuelve cuando la acción ha finalizado y puede devolver el resultado. Esto detiene el programa durante el tiempo que dure la acción.
Ein asynchrones Modell ermöglicht es, dass mehrere Dinge gleichzeitig passieren. Wenn Sie eine Aktion starten, wird das Programm weiter ausgeführt. Wenn die Aktion endet, wird das Programm informiert und hat Zugriff auf das Ergebnis (z. B. von der Festplatte gelesene Daten).
Wir können die synchrone und asynchrone Programmierung anhand eines kleinen Beispiels vergleichen: Ein Programm, das zwei Ressourcen aus dem Netzwerk erhält und dann die Ergebnisse kombiniert.
In einer synchronen Umgebung, in der die Anforderungsfunktion erst nach Ausführung ihrer Aufgabe zurückkehrt, besteht die einfachste Möglichkeit, diese Aufgabe auszuführen, darin, die Anforderungen nacheinander auszuführen. Dies hat den Nachteil, dass die zweite Anforderung erst startet, wenn die erste abgeschlossen ist. Die insgesamt benötigte Zeit ist mindestens die Summe der beiden Antwortzeiten.
Die Lösung für dieses Problem besteht in einem synchronen System darin, zusätzliche Steuerungs-Threads auszuführen. Ein Thread ist ein anderes laufendes Programm, dessen Ausführung vom Betriebssystem mit anderen Programmen verwoben werden kann. Da die meisten modernen Computer mehrere Prozessoren enthalten, können sogar mehrere Threads gleichzeitig auf verschiedenen Prozessoren ausgeführt werden. Ein zweiter Thread könnte die zweite Anforderung starten, und dann warten beide Threads auf die Rückkehr ihrer Ergebnisse. Anschließend werden sie erneut synchronisiert, um ihre Ergebnisse zu kombinieren.
Im synchronen Modell ist die vom Netzwerk benötigte Zeit Teil der Zeitachse für einen bestimmten Steuerungsthread. Im asynchronen Modell führt der Start einer Netzwerkaktion konzeptionell zu einer Aufteilung auf der Zeitachse. Das Programm, das die Aktion gestartet hat, wird weiterhin ausgeführt, und die Aktion wird daneben ausgeführt, um das Programm zu benachrichtigen, wenn es beendet wird.
Eine andere Möglichkeit, den Unterschied zu beschreiben, besteht darin, dass das Warten auf den Abschluss von Aktionen im synchronen Modell impliziert ist, während es unter unserer Kontrolle im asynchronen Modell explizit ist.
Kurze Asynchronität in beide Richtungen. Es erleichtert das Ausdrücken von Programmen, die nicht zum geradlinigen Steuerungsmodell passen, aber es kann auch Ausdrucksprogramme, die einer geraden Linie folgen, umständlicher machen. Wir werden später in diesem Kapitel nach Möglichkeiten suchen, um dieses Unbehagen zu beheben.
Las dos importantes plataformas de programación JavaScript (Browser y Node.js) realizan operaciones que pueden tardar un tiempo en ser asincrónicas, en lugar de depender de sub-procesos. Dado que la programación con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno.
Crow-Technologie
Die meisten Menschen sind sich der Tatsache bewusst, dass Krähen sehr intelligente Vögel sind. Sie können Werkzeuge verwenden, vorausplanen, sich an Dinge erinnern und sogar miteinander kommunizieren.
Was die meisten Menschen nicht wissen, ist, dass sie zu vielen Dingen fähig sind, die sie uns gut verbergen. Ein bekannter (wenn auch etwas exzentrischer) Experte für Korviden hat mir gesagt, dass die Raben-Technologie nicht weit unter der menschlichen Technologie liegt und dass sie aufholen.
Zum Beispiel haben viele Krähenkulturen die Fähigkeit, Computergeräte zu bauen. Diese sind nicht elektronisch, ebenso wie menschliche Computergeräte, sondern wirken durch die Handlungen winziger Insekten, einer Art, die eng mit der Termite verwandt ist und eine symbiotische Beziehung zu Krähen entwickelt hat. Die Vögel versorgen sie mit Nahrung, und im Gegenzug bauen und betreiben die Insekten ihre komplexen Kolonien, die mit Hilfe der Lebewesen Berechnungen durchführen.
Diese Kolonien befinden sich normalerweise in großen und langlebigen Nestern. Vögel und Insekten arbeiten zusammen, um ein Netzwerk von Knollentonstrukturen aufzubauen, die zwischen den Zweigen des Nestes versteckt sind und in denen die Insekten leben und arbeiten.
Für die Kommunikation mit anderen Geräten verwenden diese Maschinen Lichtsignale. Die Krähen betten reflektierende Materialstücke in spezielle Kommunikationsstiele ein, und die Insekten lenken sie, um das Licht in ein anderes Nest zu reflektieren, und codieren die Daten als eine Folge schneller Blitze. Dies bedeutet, dass nur Nester mit einer ununterbrochenen visuellen Verbindung kommunizieren können.
Unser Freund, der Experte für Korviden, hat das Netzwerk der Krähennester in der Stadt kartiert Hières-sur-Amby, am Ufer der Rhone.
In einem erstaunlichen Beispiel für konvergente Evolution führen Rabencomputer JavaScript aus. In diesem Kapitel werden wir einige grundlegende Netzwerkfunktionen für sie schreiben.
Rückrufe
Ein Ansatz zur asynchronen Programmierung besteht darin, langsam wirkende Funktionen ein zusätzliches Argument, eine Rückruffunktion, zu verwenden. Die Aktion wird gestartet, und wenn sie beendet ist, wird die Rückruffunktion mit dem Ergebnis aufgerufen.
Zum Beispiel die Funktion setTimeout, verfügbar sowohl in Node.js wie in der Browser, Es wartet auf eine bestimmte Anzahl von Millisekunden (eine Sekunde entspricht tausend Millisekunden) und ruft dann eine Funktion auf.
setTimeout (() => console.log ("Tick"), 500);
Normalerweise ist das Warten keine sehr wichtige Art von Arbeit, aber es kann nützlich sein, wenn Sie beispielsweise eine Animation aktualisieren oder prüfen, ob etwas länger als eine bestimmte Zeit dauert.
Wenn Sie mit der Rückruffunktion mehrere asynchrone Aktionen in einer Zeile ausführen, müssen Sie immer wieder neue Funktionen übergeben, um die Fortsetzung der Berechnung nach den Aktionen zu ermöglichen.
Die meisten Computer in Krähennest Sie verfügen über eine Langzeit-Datenspeicherlampe, in die Informationen auf Zweige geätzt werden, damit sie später abgerufen werden können. Das Speichern oder Suchen nach Daten dauert einen Moment, daher ist die Schnittstelle für die Langzeitspeicherung asynchron und verwendet Rückruffunktionen.
Las bombillas de almacenamiento almacenan los datos codificados por JSON bajo nombres. Un cuervo puede almacenar información sobre los lugares donde se esconde la comida bajo el nombre de «cachés de comida», que puede contener una serie de nombres que apuntan a otras piezas de datos, describiendo la Zwischenspeicher real. Para buscar un caché de comida en los bulbos de almacenamiento del nido de roble grande, un cuervo podría ejecutar un código como este:
{bigOak} aus "./crow-tech" importieren; bigOak.readStorage ("Lebensmittel-Caches", Caches => {let firstCache = Caches [0]; bigOak.readStorage (firstCache, info => {console.log (info);});});
(Alle Namen und Zeichenfolgen wurden aus der Crow-Sprache ins Englische übersetzt.
Diese Art der Programmierung ist machbar, aber die Einrückungsstufe nimmt mit jeder asynchronen Aktion zu, da sie in einer anderen Funktion endet. Kompliziertere Dinge wie das gleichzeitige Ausführen mehrerer Aktionen können etwas umständlich sein.
Crows Nest-Computer sind für die Kommunikation über Request-Response-Paare ausgelegt. Dies bedeutet, dass ein Nest eine Nachricht an ein anderes Nest sendet, das sofort eine Nachricht zurücksendet, den Empfang bestätigt und möglicherweise eine Antwort auf eine in der Nachricht gestellte Frage enthält.
Jede Nachricht ist mit einem Typ versehen, der bestimmt, wie sie behandelt wird. Unser Code kann Handler für bestimmte Arten von Anforderungen definieren. Wenn eine Anforderung dieses Typs empfangen wird, wird der Handler aufgerufen, um eine Antwort zu erstellen.
Die vom Modul exportierte Schnittstelle "./Crow-tech" proporciona funciones basadas en la callback para la comunicación. Los nidos tienen un método de envío que envía una petición. Espera el nombre del nido de destino, el tipo de solicitud y el Inhalt de la solicitud como sus tres primeros argumentos, y espera que una función llame cuando se reciba una respuesta como su cuarto y último argumento.
bigOak.send ("Kuhweide", "Notiz", "Lassen Sie uns um 19 Uhr laut krächzen", () => console.log ("Notiz geliefert."));
Um Nester in die Lage zu versetzen, diese Anfrage zu empfangen, müssen wir zunächst einen Anfragetyp definieren, der als "Notiz" bezeichnet wird. Der Code, der die Anforderungen verarbeitet, muss nicht nur in diesem Computernest ausgeführt werden, sondern auch in allen Nestern, die Nachrichten dieses Typs empfangen können. Wir gehen davon aus, dass eine Krähe fliegt und unseren Code in allen Nestern installiert.
{defineRequestType} aus "./crow-tech" importieren; defineRequestType ("note", (nest, content, source, done) => {console.log (`$ {nest.name} empfangene Notiz: $ {content}`); done ();});
Die Funktion defineRequestType definiert einen neuen Anforderungstyp. Das Beispiel fügt Unterstützung für "Notiz" -Anfragen hinzu, die einfach eine Notiz an ein bestimmtes Nest senden. Unsere Implementierung fordert console.log damit wir überprüfen können, ob die Anfrage eingegangen ist. Nester haben eine Eigenschaft mit einem Namen, der ihren Namen enthält.
Das vierte Argument, das dem Handler gegeben wird, ist eine Rückruffunktion, die er aufrufen sollte, wenn die Anforderung endet. Wenn wir den Rückgabewert des Controllers als Antwortwert verwendet hätten, würde dies bedeuten, dass ein Anforderungscontroller keine asynchronen Aktionen ausführen kann. Eine Funktion, die asynchrone Arbeit ausführt, kehrt normalerweise vor Beendigung der Arbeit zurück, nachdem ein Rückruf nach Abschluss aufgerufen wurde. Wir benötigen also einen asynchronen Mechanismus (in diesem Fall eine andere Rückruffunktion), um zu signalisieren, wann eine Antwort verfügbar ist.
In gewisser Weise ist Asynchronität ansteckend. Jede Funktion, die eine asynchron arbeitende Funktion aufruft, muss asynchron sein und einen Rückrufmechanismus oder ähnliches verwenden, um das Ergebnis zu liefern. Das Aufrufen eines Rückrufs ist etwas komplizierter und fehleranfälliger als das Zurückgeben eines Werts. Daher ist es nicht gut, große Teile Ihres Programms auf diese Weise strukturieren zu müssen.
Versprechen
Trabajar con conceptos abstractos es a menudo más fácil cuando esos conceptos pueden ser representados por valores. En el caso de acciones asincrónicas, en lugar de organizar la llamada de una función en algún momento futuro, se puede devolver un objeto que represente este Veranstaltung futuro.
Dafür ist die Standard-Promise-Klasse gedacht. Ein Versprechen ist eine asynchrone Aktion, die irgendwann abgeschlossen werden und einen Wert erzeugen kann. Es kann jeden, der interessiert ist, benachrichtigen, wenn sein Wert verfügbar ist.
Der einfachste Weg, ein Versprechen zu erstellen, ist ein Anruf Promise.resolve. Diese Funktion stellt sicher, dass der ihm gegebene Wert in ein Versprechen eingewickelt wird. Wenn es bereits ein Versprechen ist, wird es einfach zurückgegeben; Andernfalls erhalten Sie ein neues Versprechen, das seinen Wert sofort beendet.
sei fünfzehn = Promise.resolve (15); fünfzehn.then (value => console.log (`Got $ {value}`)); // → 15 bekommen
Um das Ergebnis eines Versprechens zu erhalten, können Sie die then-Methode verwenden. Dies registriert eine Rückruffunktion, die aufgerufen werden soll, wenn das Versprechen aufgelöst wird und einen Wert erzeugt. Sie können einem einzelnen Versprechen mehrere Rückrufe hinzufügen, die auch dann aufgerufen werden, wenn Sie sie hinzufügen, nachdem das Versprechen bereits gelöst (beendet) wurde.
Aber das ist dann noch nicht alles. Gibt ein weiteres Versprechen zurück, das den Wert auflöst, den die Controller-Funktion zurückgibt, oder, wenn dies ein Versprechen zurückgibt, auf dieses Versprechen wartet und dann sein Ergebnis auflöst.
Es ist nützlich, sich Versprechen als ein Mittel vorzustellen, um Werte in die asynchrone Realität umzusetzen. Ein normaler Wert ist einfach da. Eine versprochene Sicherheit ist eine Sicherheit, die möglicherweise bereits vorhanden ist oder zu einem späteren Zeitpunkt angezeigt wird. In Form von Versprechungen definierte Berechnungen wirken sich auf diese umschlossenen Werte aus und werden asynchron ausgeführt, sobald die Werte verfügbar werden.
Um ein Versprechen zu erstellen, können Sie Promise als Konstruktor verwenden. Es hat eine etwas seltsame Schnittstelle: Der Konstruktor erwartet eine Funktion als Argument, das er sofort aufruft und ihm eine Funktion übergibt, mit der er das Versprechen auflösen kann. Dies funktioniert auf diese Weise und nicht beispielsweise mit einer Auflösungsmethode, sodass nur der Code, der das Versprechen erstellt hat, es auflösen kann.
Auf diese Weise erstellen Sie eine vielversprechende Schnittstelle für die Funktion readStorage:
Funktionsspeicher (Nest, Name) {neues Versprechen zurückgeben (Auflösung => {nest.readStorage (Name, Ergebnis => Auflösung (Ergebnis));}); } storage (bigOak, "Feinde") .then (value => console.log ("Got", value));
Diese asynchrone Funktion gibt einen signifikanten Wert zurück. Dies ist der Hauptvorteil von Versprechungen: Sie vereinfachen die Verwendung asynchroner Funktionen. Anstatt Rückrufe weitergeben zu müssen, sehen versprechungsbasierte Funktionen ähnlich aus wie normale: Sie nehmen die Daten als Argumente und geben ihre Ergebnisse zurück. Der einzige Unterschied besteht darin, dass die Ausgabe möglicherweise noch nicht verfügbar ist.
Fehler
Regelmäßige JavaScript-Berechnungen können fehlschlagen, wenn eine Ausnahme gemacht wird. Asynchrone Berechnungen benötigen häufig so etwas. Eine Netzwerkanforderung kann fehlschlagen, oder ein Code, der Teil der asynchronen Berechnung ist, kann eine Ausnahme sein.
Eines der dringendsten Probleme beim Rückrufstil der asynchronen Programmierung besteht darin, dass es äußerst schwierig ist, sicherzustellen, dass Fehler ordnungsgemäß an Rückrufe gemeldet werden.
Eine weit verbreitete Konvention ist, dass das erste Argument des Rückrufs verwendet wird, um anzuzeigen, dass die Aktion fehlgeschlagen ist, und das zweite den Wert enthält, der von der Aktion erzeugt wurde, als sie erfolgreich war. Diese Rückruffunktionen sollten immer nach einer Ausnahme suchen und sicherstellen, dass alle von ihnen verursachten Probleme, einschließlich Ausnahmen, die von den von ihnen aufgerufenen Funktionen ausgelöst werden, abgefangen und der richtigen Funktion zugewiesen werden.
Versprechen machen es einfacher. Sie können aufgelöst (die Aktion erfolgreich abgeschlossen) oder abgelehnt (fehlgeschlagen) werden. Resolve-Handler (wie zu diesem Zeitpunkt registriert) werden nur aufgerufen, wenn die Aktion erfolgreich ist, und Ablehnungen werden automatisch an das neue Versprechen weitergegeben, das bis dahin zurückgegeben wird. Und wenn ein Handler eine Ausnahme auslöst, wird das durch seinen Aufruf erzeugte Versprechen automatisch abgelehnt. Wenn also ein Element in einer Kette asynchroner Aktionen fehlschlägt, wird das Ergebnis der gesamten Kette als abgelehnt markiert, und über den Punkt hinaus, an dem es fehlgeschlagen ist, wird kein Erfolgshandler aufgerufen.
Ebenso wie das Auflösen eines Versprechens einen Wert liefert, liefert das Ablehnen eines Versprechens auch einen, der normalerweise als Grund für die Ablehnung bezeichnet wird. Wenn eine Ausnahme in einer Handlerfunktion eine Zurückweisung verursacht, wird der Ausnahmewert als Grund verwendet. Wenn ein Handler ein abgelehntes Versprechen zurückgibt, fließt diese Ablehnung zum nächsten Versprechen. Es gibt eine Funktion Promise.reject das schafft ein neues Versprechen, sofort abgelehnt.
Um solche Ablehnungen explizit zu behandeln, verfügen Versprechen über eine Fangmethode, die einen Handler registriert, der aufgerufen werden soll, wenn das Versprechen abgelehnt wird, ähnlich wie Handler mit normaler Auflösung umgehen. Es ist auch sehr ähnlich in dem Sinne, dass es ein neues Versprechen zurückgibt, das sich auf den Wert des ursprünglichen Versprechens auflöst, wenn es normal aufgelöst wird, und auf das Ergebnis des Catch-Handlers ansonsten. Wenn ein Fangmanager einen Fehler auslöst, wird das neue Versprechen ebenfalls abgelehnt.
Kurz gesagt, es akzeptiert auch einen Ablehnungshandler als zweites Argument, sodass Sie beide Arten von Handlern in einem einzigen Methodenaufruf installieren können.
Eine an den Promise-Konstruktor übergebene Funktion erhält zusammen mit der Auflösungsfunktion ein zweites Argument, mit dem sie das neue Versprechen ablehnen kann.
Die Zeichenfolgen von Versprechungswerten, die durch die Aufrufe an diesem Punkt und den Fang erstellt wurden, können als Pipeline angesehen werden, durch die sich asynchrone Werte oder Fehler bewegen. Da diese Ketten durch Registrieren von Handlern erstellt werden, ist jedem Link ein Erfolgshandler oder ein Ablehnungshandler (oder beide) zugeordnet. Handler, die nicht dem Ergebnistyp (Erfolg oder Misserfolg) entsprechen, werden ignoriert. Aber diejenigen, die übereinstimmen, werden aufgerufen, und ihr Ergebnis bestimmt, welche Art von Wert als nächstes kommt: Erfolg, wenn ein Wert ohne Versprechen zurückgegeben wird, Ablehnung, wenn eine Ausnahme ausgelöst wird, und das Ergebnis eines Versprechens, wenn einer dieser Werte zurückgegeben wird.
neues Versprechen ((_, ablehnen) => ablehnen (neuer Fehler ("Fehler")) .then (Wert => console.log ("Handler 1")) .catch (Grund => {console.log ("Gefangen) Fehler "+ Grund);" nichts "zurückgeben;}) .then (Wert => console.log (" Handler 2 ", Wert)); // → Fehler gefangen Fehler: Fehler // → Handler 2 nichts
So wie eine nicht erfasste Ausnahme von der Umgebung behandelt wird, können JavaScript-Umgebungen erkennen, wenn eine Versprechen-Ablehnung nicht behandelt wird, und sie als Fehler melden.