7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. JavaScript

Eine Einführung in die asynchronen ES7-Funktionen

Read Time: 12 mins

German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)

Wenn Sie der Welt von JavaScript gefolgt sind, haben Sie wahrscheinlich von Versprechungen gehört. Es gibt einige großartige Online-Tutorials, wenn Sie mehr über Versprechen erfahren möchten, aber ich werde sie hier nicht erklären. In diesem Artikel wird davon ausgegangen, dass Sie bereits über Kenntnisse in Bezug auf Versprechen verfügen.

Versprechen werden als die Zukunft der asynchronen Programmierung in JavaScript angepriesen. Versprechen sind wirklich großartig und helfen bei der Lösung vieler Probleme, die bei der asynchronen Programmierung auftreten, aber diese Behauptung ist nur einigermaßen richtig. In Wirklichkeit sind Versprechen die Grundlage für die Zukunft der asynchronen Programmierung in JavaScript. Im Idealfall werden Versprechen hinter den Kulissen versteckt und wir können unseren asynchronen Code so schreiben, als wäre er synchron.

In ECMAScript 7 wird dies mehr als ein phantasievoller Traum: Es wird Realität, und ich werde Ihnen diese Realität - sogenannte asynchrone Funktionen - jetzt zeigen. Warum reden wir jetzt darüber? Immerhin ist ES6 noch nicht vollständig fertiggestellt. Wer weiß also, wie lange es dauern wird, bis wir ES7 sehen? Die Wahrheit ist, dass Sie diese Technologie jetzt verwenden können, und am Ende dieses Beitrags werde ich Ihnen zeigen, wie.

Der aktuelle Stand der Dinge

Bevor ich anfange zu demonstrieren, wie man asynchrone Funktionen verwendet, möchte ich einige Beispiele mit Versprechungen (unter Verwendung von ES6-Versprechungen) durchgehen. Später werde ich diese Beispiele konvertieren, um asynchrone Funktionen zu verwenden, damit Sie sehen können, welchen großen Unterschied dies macht.

Beispiele

In unserem ersten Beispiel machen wir etwas wirklich Einfaches: Aufrufen einer asynchronen Funktion und Protokollieren des zurückgegebenen Werts.

Nachdem wir dieses grundlegende Beispiel definiert haben, wollen wir uns etwas Komplizierterem zuwenden. Ich werde Beispiele aus einem Beitrag in meinem eigenen Blog verwenden und ändern, in dem einige Muster für die Verwendung von Versprechungen in verschiedenen Szenarien behandelt werden. Jedes der Beispiele ruft asynchron ein Array von Werten ab, führt eine asynchrone Operation aus, die jeden Wert im Array transformiert, jeden neuen Wert protokolliert und schließlich das mit den neuen Werten gefüllte Array zurückgibt.

Zuerst sehen wir uns ein Beispiel an, in dem mehrere asynchrone Vorgänge parallel ausgeführt werden, und reagieren dann sofort, wenn jeder abgeschlossen ist, unabhängig von der Reihenfolge, in der sie abgeschlossen sind. Die Funktion getValues ist dieselbe wie im vorherigen Beispiel. Die asyncOperation-Funktion wird auch in den kommenden Beispielen wiederverwendet.

Wir können genau das Gleiche tun, aber stellen Sie sicher, dass die Protokollierung in der Reihenfolge der Elemente im Array erfolgt. Mit anderen Worten, in diesem nächsten Beispiel wird die asynchrone Arbeit parallel ausgeführt, die synchrone Arbeit wird jedoch sequentiell ausgeführt:

Unser letztes Beispiel zeigt ein Muster, bei dem wir warten, bis eine vorherige asynchrone Operation abgeschlossen ist, bevor wir mit der nächsten beginnen. In diesem Beispiel läuft nichts parallel; alles ist sequentiell.

Selbst mit der Fähigkeit von Versprechungen, das Verschachteln von Rückrufen zu reduzieren, hilft es nicht wirklich viel. Das Ausführen einer unbekannten Anzahl aufeinanderfolgender asynchroner Aufrufe ist unabhängig von Ihrer Tätigkeit unübersichtlich. Es ist besonders entsetzlich, all diese verschachtelten return-Schlüsselwörter zu sehen. Wenn wir das newValues-Array durch die Versprechen im Rückruf des reduce übergeben würden, anstatt es für die gesamte foo-Funktion global zu machen, müssten wir den Code anpassen, um noch mehr verschachtelte Rückgaben zu erhalten, wie folgt:

Stimmen Sie nicht zu, dass wir das beheben müssen? Schauen wir uns die Lösung an.

Asynchrone Funktionen zur Rettung

Selbst mit Versprechungen ist die asynchrone Programmierung nicht gerade einfach und verläuft nicht immer gut von A bis Z. Die synchrone Programmierung ist so viel einfacher und wird so viel natürlicher geschrieben und gelesen. Die Spezifikation für asynchrone Funktionen untersucht ein Mittel (unter Verwendung von ES6-Generatoren hinter den Kulissen), um Ihren Code so zu schreiben, als ob er synchron wäre.

Wie benutzen wir sie?

Als erstes müssen wir unseren Funktionen das Schlüsselwort async voranstellen. Ohne dieses Schlüsselwort können wir das alles entscheidende Schlüsselwort await in dieser Funktion nicht verwenden, was ich gleich erläutern werde.

Das Schlüsselwort async ermöglicht nicht nur die Verwendung von await, sondern stellt auch sicher, dass die Funktion ein Promise-Objekt zurückgibt. Innerhalb einer asynchronen Funktion gibt die Funktion jedes Mal, wenn Sie einen Wert return, ein Promise zurück, das mit diesem Wert aufgelöst wird. Die Art der Ablehnung besteht darin, einen Fehler auszulösen. In diesem Fall ist der Ablehnungswert das Fehlerobjekt. Hier ist ein einfaches Beispiel:

Wir sind noch nicht einmal zum besten Teil gekommen und haben unseren Code bereits mehr zu synchronem Code gemacht, weil wir aufhören konnten, explizit mit dem Promise-Objekt herumzuspielen. Wir können jede Funktion übernehmen und sie dazu bringen, ein Promise-Objekt zurückzugeben, indem wir einfach das async Schlüsselwort an die Vorderseite hinzufügen.

Lassen Sie uns fortfahren und unsere Funktionen getValues und asyncOperation konvertieren:

Einfach! Schauen wir uns nun den besten Teil von allen an: das Schlüsselwort await. Innerhalb Ihrer asynchronen Funktion können Sie jedes Mal, wenn Sie eine Operation ausführen, die ein Versprechen zurückgibt, das Schlüsselwort await davor werfen. Die Ausführung der restlichen Funktion wird beendet, bis das zurückgegebene Versprechen aufgelöst oder abgelehnt wurde. Zu diesem Zeitpunkt wird die await promisingOperation() auf den aufgelösten oder abgelehnten Wert ausgewertet. Beispielsweise:

Wenn Sie foo aufrufen, wird entweder gewartet, bis die promisingOperation behoben ist, und dann wird der "Erfolg!" Nachricht oder promisingOperation wird abgelehnt. In diesem Fall wird die Ablehnung weitergeleitet und foo wird mit "Fehler!" abgelehnt. Da foo nichts zurückgibt, wird es mit undefined aufgelöst, vorausgesetzt, promisingOperation ist erfolgreich.

Es bleibt nur noch eine Frage: Wie beheben wir Fehler? Die Antwort auf diese Frage ist einfach: Alles, was wir tun müssen, ist, sie in einen try...catch-Block zu packen. Wenn eine der asynchronen Operationen abgelehnt wird, können wir das catch und damit umgehen:

Nachdem wir alle Grundlagen kennengelernt haben, gehen wir unsere vorherigen Versprechungsbeispiele durch und konvertieren sie in asynchrone Funktionen.

Beispiele

Im ersten Beispiel oben wurde getValues erstellt und verwendet. Wir haben getValues bereits neu erstellt, daher müssen wir nur den Code für die Verwendung neu erstellen. Es gibt eine mögliche Einschränkung für asynchrone Funktionen, die hier angezeigt wird: Der Code muss sich in einer Funktion befinden. Das vorherige Beispiel befand sich im globalen Bereich (soweit irgendjemand es beurteilen konnte), aber wir müssen unseren asynchronen Code in eine asynchrone Funktion einschließen, damit er funktioniert:

Selbst wenn der Code in eine Funktion eingeschlossen wird, behaupte ich immer noch, dass er einfacher zu lesen ist und weniger Bytes enthält (wenn Sie den Kommentar entfernen). Wenn Sie sich richtig erinnern, macht unser nächstes Beispiel alles parallel. Dieser ist ein bisschen knifflig, weil wir eine innere Funktion haben, die ein Versprechen zurückgeben muss. Wenn wir das Schlüsselwort await in der inneren Funktion verwenden, muss dieser Funktion auch async vorangestellt werden.

Möglicherweise haben Sie das Sternchen bemerkt, das an das await Schlüsselwort angehängt ist. Dies scheint noch ein wenig zur Debatte zu stehen, aber es sieht so aus, als würde das await* den Ausdruck in Promise.all im Wesentlichen automatisch rechts von ihm umbrechen. Im Moment unterstützt das Tool, das wir uns später ansehen werden, das await* nicht. Daher sollte es so konvertiert werden, dass es auf await Promise.all(newValues);. wie wir es im nächsten Beispiel tun.

Im nächsten Beispiel werden die asyncOperation-Aufrufe parallel ausgelöst, aber dann wird alles wieder zusammengeführt und die Ausgabe nacheinander ausgeführt.

Ich liebe das. Das ist extrem sauber. Wenn wir die Schlüsselwörter await und async entfernen, den Promise.all-Wrapper entfernen und getValues und asyncOperation synchronisieren würden, würde dieser Code immer noch genauso funktionieren, außer dass er synchron wäre. Das ist im Wesentlichen das, was wir erreichen wollen.

In unserem letzten Beispiel läuft natürlich alles nacheinander. Es werden keine asynchronen Operationen ausgeführt, bis die vorherige abgeschlossen ist.

Wieder einmal machen wir eine innere Funktion async. In diesem Code ist eine interessante Eigenart enthalten. Ich habe [] als "Memo"-Wert zum reduce übergeben, aber dann habe ich darauf await. Der Wert rechts vom await muss kein Versprechen sein. Es kann jeden Wert annehmen, und wenn es kein Versprechen ist, wird es nicht darauf warten; es wird nur synchron ausgeführt. Natürlich werden wir nach der ersten Ausführung des Rückrufs tatsächlich mit einem Versprechen arbeiten.

Dieses Beispiel ist ziemlich genau wie das erste Beispiel, außer dass wir reduce anstelle von map verwenden, damit wir auf die vorherige Operation await können, und dann, weil wir reduce verwenden, um ein Array zu erstellen (was Sie normalerweise nicht tun würden). Insbesondere wenn Sie ein Array mit der gleichen Größe wie das ursprüngliche Array erstellen, müssen wir das Array innerhalb des Rückrufs erstellen, um es zu reduce.

Async-Funktionen heute verwenden 

Jetzt, da Sie einen Einblick in die Einfachheit und Attraktivität asynchroner Funktionen erhalten haben, weinen Sie möglicherweise so, wie ich es beim ersten Mal getan habe, als ich sie gesehen habe. Ich weinte nicht vor Freude (obwohl ich es fast getan hätte); Nein, ich habe geweint, weil ES7 nicht hier sein wird, bis ich sterbe! Zumindest fühlte ich mich so. Dann habe ich von Traceur erfahren.

Traceur wird von Google geschrieben und gepflegt. Es ist ein Transpiler, der ES6-Code in ES5 konvertiert. Das hilft nicht! Nun, es wäre nicht so, außer dass sie auch Unterstützung für asynchrone Funktionen implementiert haben. Es ist immer noch eine experimentelle Funktion, was bedeutet, dass Sie dem Compiler explizit mitteilen müssen, dass Sie diese Funktion verwenden, und dass Sie Ihren Code auf jeden Fall gründlich testen möchten, um sicherzustellen, dass es keine Probleme mit der Kompilierung gibt.

Die Verwendung eines Compilers wie Traceur bedeutet, dass leicht aufgeblähter, hässlicher Code an den Client gesendet wird, was nicht das ist, was Sie wollen. Wenn Sie jedoch Quellkarten verwenden, werden die meisten Nachteile im Zusammenhang mit der Entwicklung im Wesentlichen beseitigt. Sie lesen, schreiben und debuggen sauberen ES6 / 7-Code, anstatt einen verschlungenen Code durcheinander zu bringen, zu schreiben und zu debuggen, der die Einschränkungen der Sprache umgehen muss.

Natürlich ist die Codegröße immer noch größer als wenn Sie den ES5-Code (höchstwahrscheinlich) von Hand geschrieben hätten. Daher müssen Sie möglicherweise ein Gleichgewicht zwischen wartbarem Code und performantem Code finden, aber das ist ein Gleichgewicht, das Sie häufig benötigen auch ohne Verwendung eines Transpilers zu finden.

Traceur verwenden

Traceur ist ein Befehlszeilenprogramm, das über NPM installiert werden kann:

Im Allgemeinen ist Traceur recht einfach zu verwenden, aber einige der Optionen können verwirrend sein und einige Experimente erfordern. Sie können eine Liste der Optionen für weitere Details sehen. Das, woran wir wirklich interessiert sind, ist die --experimental Option.

Sie müssen diese Option verwenden, um die experimentellen Funktionen zu aktivieren. Auf diese Weise funktionieren asynchrone Funktionen. Sobald Sie eine JavaScript-Datei (in diesem Fall main.js) mit ES6-Code und asynchronen Funktionen haben, können Sie sie einfach folgendermaßen kompilieren:

Sie können den Code auch einfach ausführen, indem Sie die Datei --out compiled.js weglassen. Sie werden nicht viel sehen, es sei denn, der Code enthält console.log-Anweisungen (oder andere Konsolenausgaben), aber zumindest können Sie nach Fehlern suchen. Sie werden es wahrscheinlich in einem Browser ausführen wollen. In diesem Fall müssen Sie noch einige weitere Schritte ausführen.

  1. Laden Sie das Skript traceur-runtime.js herunter. Es gibt viele Möglichkeiten, es zu bekommen, aber eine der einfachsten ist von NPM: npm install traceur-runtime. Die Datei ist dann als index.js im Ordner dieses Moduls verfügbar.
  2. Fügen Sie in Ihrer HTML-Datei ein script-Tag hinzu, um das Traceur Runtime-Skript abzurufen.
  3. Fügen Sie unter dem Traceur Runtime-Skript ein weiteres script-Tag hinzu, um compiled.js abzurufen.

Danach sollte Ihr Code betriebsbereit sein!

Automatisierung der Traceur-Kompilierung

Sie können nicht nur das Traceur-Befehlszeilentool verwenden, sondern auch die Kompilierung automatisieren, sodass Sie nicht immer wieder zu Ihrer Konsole zurückkehren und den Compiler erneut ausführen müssen. Grunt und Gulp, die automatisierte Task-Läufer sind, haben jeweils ihre eigenen Plugins, mit denen Sie die Traceur-Kompilierung automatisieren können: grunt-traceur bzw. gulp-traceur.

Jeder dieser Task-Runner kann so eingerichtet werden, dass er Ihr Dateisystem überwacht und den Code neu kompiliert, sobald Sie Änderungen an Ihren JavaScript-Dateien speichern. Informationen zur Verwendung von Grunt oder Gulp finden Sie in der Dokumentation "Erste Schritte".

Abschluss

Die asynchronen Funktionen von ES7 bieten Entwicklern die Möglichkeit, die Callback-Hölle auf eine Weise zu verlassen, die Versprechen allein niemals könnten. Mit dieser neuen Funktion können wir asynchronen Code auf eine Weise schreiben, die unserem synchronen Code sehr ähnlich ist, und obwohl ES6 noch auf seine vollständige Veröffentlichung wartet, können wir asynchrone Funktionen bereits heute durch Transpilation verwenden. Worauf warten Sie? Gehen Sie raus und machen Sie Ihren Code großartig!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Scroll to top
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.