Ereignisbasierte Programmierung Was Async über Synchronisation hat

Eine der Stärken von JavaScript ist die Art und Weise, wie es mit asynchronem Code (kurz asynchron) umgeht. Anstatt den Thread zu blockieren, wird asynchroner Code in eine Ereigniswarteschlange verschoben, die ausgelöst wird, nachdem alle anderen Code ausgeführt wurden. Es kann jedoch für Anfänger schwierig sein, asynchronem Code zu folgen. Ich werde helfen, die Verwirrung in diesem Artikel aufzuklären.


Async-Code verstehen

Die grundlegendsten asynchronen Funktionen von JavaScript sind setTimeout und setInterval. Das setTimeout Eine Funktion führt eine bestimmte Funktion aus, nachdem eine bestimmte Zeit vergangen ist. Es akzeptiert eine Rückruffunktion als erstes Argument und eine Zeit (in Millisekunden) als zweites Argument. Hier ist ein Beispiel für seine Verwendung:

 console.log ("a"); setTimeout (function () console.log ("c"), 500); setTimeout (function () console.log ("d"), 500); setTimeout (function () console.log ("e"), 500); console.log ("b");

Wie erwartet, gibt die Konsole "a", "b" und dann 500 ms (ish) später aus. Wir sehen "c", "d" und "e". Ich benutze "ish" da setTimeout ist eigentlich unvorhersehbar. In der Tat spricht sogar die HTML5-Spezifikation über dieses Problem:

"Diese API garantiert nicht, dass die Timer genau nach Zeitplan ausgeführt werden. Verzögerungen aufgrund von CPU-Auslastung, anderen Aufgaben usw. sind zu erwarten."

Interessanterweise wird ein Timeout erst ausgeführt, wenn der restliche Code in einem Block ausgeführt wurde. Wenn also ein Timeout eingestellt ist und dann eine lange Funktion ausgeführt wird, beginnt das Timeout erst, wenn diese lange Funktion beendet ist. In der Tat funktioniert asynchron wie setTimeout und setInterval werden in eine Warteschlange geschoben, die als bekannt ist Ereignisschleife.

Das Ereignisschleife ist eine Warteschlange von Rückruffunktionen. Wenn eine async-Funktion ausgeführt wird, wird die Rückruffunktion in die Warteschlange verschoben. Die JavaScript-Engine beginnt erst mit der Verarbeitung der Ereignisschleife, nachdem der Code nach einer asynchronen Funktion ausgeführt wurde. Dies bedeutet, dass JavaScript-Code nicht multithreadig ist, obwohl es so scheint. Die Ereignisschleife ist eine FIFO-Warteschlange (First-In-First-Out), dh, Callbacks werden in der Reihenfolge ausgeführt, in der sie der Warteschlange hinzugefügt wurden. JavaScript wurde für die Sprache des Knotens gewählt, da es so einfach ist, diese Art von Code zu schreiben.


Ajax

Asynchrones JavaScript und XML (Ajax) haben die Landschaft von JavaScript für immer verändert. Ganz plötzlich konnte ein Browser eine Webseite aktualisieren, ohne sie neu laden zu müssen. Der Code für die Implementierung von Ajax in verschiedenen Browsern kann lange und langwierig sein. Dank jQuery (und anderer Bibliotheken) wurde Ajax jedoch zu einer äußerst einfachen und eleganten Lösung, um die Client-Server-Kommunikation zu vereinfachen.

Daten werden mit jQuery asynchron abgerufen $ .ajax ist ein einfacher Cross-Browser-Prozess, aber es ist nicht sofort ersichtlich, was genau hinter den Kulissen passiert. Zum Beispiel:

var Daten; $ .ajax (url: "some / url / 1", success: function (data) // Aber das wird! console.log (data);) // Hoppla, das wird nicht funktionieren ... Konsole .Logdaten );

Es ist üblich, aber falsch, davon auszugehen, dass die Daten sofort nach dem Aufruf verfügbar sind $ .ajax, Was aber tatsächlich passiert ist folgendes:

xmlhttp.open ("GET", "some / ur / 1", true); xmlhttp.onreadystatechange = Funktion (Daten) if (xmlhttp.readyState === 4) console.log (Daten); ; xmlhttp.send (null);

Das zugrunde liegende XmlHttpRequest (XHR) -Objekt sendet die Anforderung, und die Rückruffunktion ist so eingestellt, dass sie die XHRs behandelt readystatechange Veranstaltung. Dann die XHR senden Methode wird ausgeführt. Da das XHR seine Arbeit verrichtet, eine interne readystatechange Ereignis feuert jedes Mal die readyState Diese Eigenschaft ändert sich. Erst wenn das XHR eine Antwort vom Remote-Host erhalten hat, wird die Callback-Funktion ausgeführt.


Mit Async-Code arbeiten

Die asynchrone Programmierung eignet sich für das, was allgemein als "Callback-Hölle" bezeichnet wird. Da praktisch alle asynchronen Funktionen in JavaScript Callbacks verwenden, führt die Ausführung mehrerer sequentieller asynchroner Funktionen zu vielen verschachtelten Callbacks, was zu schwer lesbarem Code führt.

Viele der Funktionen in node.js sind asynchron. Daher ist Code wie der folgende üblich.

var fs = required ("fs"); fs.exists ("index.js", function () fs.readFile ("index.js", "utf8", function (err, Inhalt) Inhalt = someFunction (Inhalt); // etwas mit dem Inhalt fs tun. writeFile ("index.js", "utf8", function () console.log ("puh! Fertig gemacht ...");););); console.log ("Ausführen ...");

Es ist auch üblich, clientseitigen Code wie den folgenden anzuzeigen:

GMaps.geocode (Adresse: fromAddress, Callback: Funktion (Ergebnisse, Status) if (Status == "OK") fromLatLng = Ergebnisse [0] .geometry.location; GMaps.geocode (Adresse: toAddress, Callback: Funktion (Ergebnisse, Status) if (Status == "OK") toLatLng = Ergebnisse [0] .geometry.location; map.getRoutes (origin: [fromLatLng.lat (), fromLatLng.lng ()), destination : [toLatLng.lat (), toLatLng.lng ()], travelMode: "driving", unitSystem: "imperial", callback: function (e) console.log ("ANNNND ENDLICH hier sind die Anweisungen ..."); // etwas mit e machen);););

Verschachtelte Callbacks können sehr unangenehm werden, aber es gibt verschiedene Lösungen für diesen Codierstil.

Das Problem ist nicht die Sprache selbst; Es ist die Art und Weise, wie Programmierer die Sprache verwenden - Async Javascript.

Benannte Funktionen

Eine einfache Lösung, die verschachtelte Rückrufe bereinigt, vermeidet das Schachteln von mehr als zwei Ebenen. Übergeben Sie keine anonymen Funktionen an die Rückmeldungsargumente, sondern eine benannte Funktion:

var fromLatLng, toLatLng; var routeDone = function (e) console.log ("ANNNNN ENDLICH hier sind die Anweisungen ..."); // etwas mit e machen; var toAddressDone = Funktion (Ergebnisse, Status) if (Status == "OK") toLatLng = Ergebnisse [0] .geometry.location; map.getRoutes (origin: [fromLatLng.lat (), fromLatLng.lng ()), Ziel: [toLatLng.lat (), toLatLng.lng ()], travelMode: "driving", unitSystem: "imperial", Rückruf : routeDone); ; var fromAddressDone = Funktion (Ergebnisse, Status) if (Status == "OK") fromLatLng = Ergebnisse [0] .geometry.location; GMaps.geocode (address: toAddress, Rückruf: toAddressDone); ; GMaps.geocode (address: fromAddress, callback: fromAddressDone);

Darüber hinaus kann die Bibliothek async.js bei der Bearbeitung mehrerer Ajax-Anforderungen / -Antworten helfen. Zum Beispiel:

async.parallel ([Funktion (erledigt) GMaps.geocode (Adresse: Adresse, Rückruf: Funktion (Ergebnis) Fertig (Null, Ergebnis);); Funktion (Fertig) GMaps.geocode (Adresse : fromAddress, callback: function (result) done (null, result););], function (Fehler, Ergebnisse) getRoute (results [0], results [1]););

Dieser Code führt die beiden asynchronen Funktionen aus, und jede Funktion akzeptiert einen "done" -Rückruf, der ausgeführt wird, nachdem die async-Funktion abgeschlossen ist. Wenn beide "Fertig" -Rückrufe beendet sind, wird die parallel Der Callback der Funktion führt alle Fehler oder Ergebnisse der beiden asynchronen Funktionen aus und behandelt sie.

Versprechen

Aus dem CommonJS / A:

Ein Versprechen stellt den eventuellen Wert dar, der vom einmaligen Abschluss eines Vorgangs zurückgegeben wird.

Es gibt viele Bibliotheken, die das Versprechungsmuster enthalten, und jQuery-Benutzer verfügen bereits über eine nette Versprechen-API. jQuery stellte das vor Aufgeschoben Objekt in Version 1.5 und mit dem jQuery.Deferred Konstruktor führt zu einer Funktion, die ein Versprechen zurückgibt. Eine rückversprechende Funktion führt eine Art asynchrone Operation aus und löst die Verzögerung nach Beendigung auf.

var geocode = Funktion (Adresse) var dfd = new $ .Deferred (); GMaps.geocode (Adresse: Adresse, Rückruf: Funktion (Antwort, Status) Rückgabe dfd.resolve (Antwort);; return dfd.promise (); ; var getRoute = Funktion (fromLatLng, toLatLng) var dfd = new $ .Deferred (); map.getRoutes (origin: [fromLatLng.lat (), fromLatLng.lng ()), Ziel: [toLatLng.lat (), toLatLng.lng ()], travelMode: "driving", unitSystem: "imperial", Rückruf : function (e) return dfd.resolve (e);); return dfd.promise (); ; var doSomethingCoolWithDirections = function (route) // etwas mit route tun; $ .when (Geocode (fromAddress), Geocode (toAddress)). then (function (fromLatLng, toLatLng) getRoute (vonLatLng, bisLatLng) .then (doSomethingCoolWithDirections););

Auf diese Weise können Sie zwei asynchrone Funktionen ausführen, auf ihre Ergebnisse warten und dann eine weitere Funktion mit den Ergebnissen der ersten beiden Aufrufe ausführen.

Ein Versprechen stellt den eventuellen Wert dar, der vom einmaligen Abschluss eines Vorgangs zurückgegeben wird.

In diesem Code wird der Geocode Methode wird zweimal ausgeführt und gibt ein Versprechen zurück. Die asynchronen Funktionen werden dann ausgeführt und aufgerufen Entschlossenheit in ihren Rückrufen. Dann haben beide mal angerufen Entschlossenheit, das dann wird ausgeführt und gibt die Ergebnisse der ersten beiden Aufrufe an zurück Geocode. Die Ergebnisse werden dann an übergeben getRoute, was auch ein Versprechen zurückgibt. Endlich, wenn das Versprechen von getRoute ist gelöst, die doSomethingCoolWithDirections Rückruf wird ausgeführt.

Veranstaltungen

Ereignisse sind eine weitere Lösung für die Kommunikation, wenn die Ausführung von asynchronen Rückrufen beendet ist. Ein Objekt kann zu einem Emitter werden und Ereignisse veröffentlichen, auf die andere Objekte warten können. Diese Art der Veranstaltung wird als bezeichnet Beobachtermuster. Die backbone.js-Bibliothek verfügt über diese integrierte Funktion Rückgrat.Veranstaltungen.

var SomeModel = Backbone.Model.extend (url: "/ someurl"); var SomeView = Backbone.View.extend (initialize: function () this.model.on ("reset", this.render, this); this.model.fetch ();, render: function (data)  // etwas mit Daten tun); var view = new SomeView (model: new SomeModel ());

Es gibt andere Mixin-Beispiele und -Bibliotheken zum Senden von Ereignissen, z. B. jQuery Event Emitter, EventEmitter, monologue.js und node.js verfügt über ein integriertes EventEmitter-Modul.

Die Ereignisschleife ist eine Warteschlange mit Rückruffunktionen.

Eine ähnliche Methode zum Veröffentlichen von Nachrichten verwendet die Vermittler-Muster, wird in der postal.js-Bibliothek verwendet. Im Mediator-Muster hört ein Zwischenhändler für alle Objekte auf Ereignisse und veröffentlicht sie. Bei diesem Ansatz hat ein Objekt keinen direkten Bezug zu einem anderen, wodurch die Objekte voneinander entkoppelt werden.

Geben Sie niemals ein Versprechen über eine öffentliche API zurück. Dies koppelt die API-Konsumenten an die Verwendung von Versprechen und macht das Refactoring schwierig. Eine Kombination aus Versprechungen für interne Zwecke und Eventing für externe APIs kann jedoch zu einer gut entkoppleten und testbaren App führen.

Im vorherigen Beispiel wurde die doSomethingCoolWithDirections Callback-Funktion wird ausgeführt, wenn die beiden vorherigen Geocode Funktionen sind abgeschlossen. Das doSomethingCoolWithDirections kann dann die erhaltene Antwort entgegennehmen getRoute und veröffentlichen Sie die Antwort als Nachricht.

var doSomethingCoolWithDirections = Funktion (Route) postal.channel ("ui") .publish ("direction.done", route: route); ;

Auf diese Weise können andere Bereiche der Anwendung auf den asynchronen Rückruf reagieren, ohne einen direkten Verweis auf das Objekt zur Anforderung von Anforderungen zu benötigen. Es ist möglich, dass mehrere Bereiche einer Seite aktualisiert werden müssen, wenn Anweisungen abgerufen werden. In einem typischen jQuery-Ajax-Setup müsste der Erfolgsrückruf angepasst werden, wenn eine Richtungsänderung empfangen wird. Die Wartung kann schwierig werden, aber durch die Verwendung von Messaging ist die Aktualisierung mehrerer Teile der Benutzeroberfläche viel einfacher zu handhaben.

var UI = function () this.channel = postal.channel ("ui"); this.channel.subscribe ("direction.done", this.updateDirections) .withContext (this); ; UI.prototype.updateDirections = function (data) // Die Route ist auf data.route verfügbar. Aktualisieren Sie jetzt die Benutzeroberfläche.; app.ui = neue Benutzeroberfläche ();

Einige andere Mediator-Pattern-basierte Messaging-Bibliotheken sind amplify, PubSubJS und radio.js.


Fazit

JavaScript macht das Schreiben von asynchronem Code sehr einfach. Die Verwendung von Versprechungen, Ereignissen oder benannten Funktionen beseitigt die unangenehme "Rückruf-Hölle". Weitere Informationen zu asynchronem JavaScript finden Sie unter Async-JavaScript: Erstellen Sie responsivere Apps mit weniger Code. Viele der Beispiele aus dem Beitrag befinden sich in einem Github-Repository mit dem Namen NetTutsAsyncJS. Clone weg!