Der Autor hat den COVID-19-Hilfsfonds ausgewählt, um eine Spende im Rahmen des Write for Donations-Programms zu erhalten.
Einführung
In den Anfangstagen des Internets bestanden Websites oft aus statischen Daten in einer HTML-Seite. Da jedoch Webanwendungen interaktiver und dynamischer geworden sind, ist es zunehmend erforderlich geworden, intensive Operationen wie das Herstellen externer Netzwerkanfragen zur Abfrage von API-Daten durchzuführen. Um diese Operationen in JavaScript zu behandeln, muss ein Entwickler asynchrone Programmierung-Techniken verwenden.
Da JavaScript eine single-threaded-Programmiersprache mit einem synchronen Ausführungsmodell ist, das eine Operation nach der anderen verarbeitet, kann es nur eine Anweisung gleichzeitig verarbeiten. Eine Aktion wie das Anfordern von Daten aus einer API kann jedoch eine unbestimmte Zeit in Anspruch nehmen, abhängig von der Größe der angeforderten Daten, der Geschwindigkeit der Netzwerkverbindung und anderen Faktoren. Wenn API-Aufrufe synchron durchgeführt würden, könnte der Browser keine Benutzereingaben wie Scrollen oder Klicken auf eine Schaltfläche verarbeiten, bis diese Operation abgeschlossen ist. Dies wird als Blocking bezeichnet.
Um blockierendes Verhalten zu verhindern, verfügt die Browserumgebung über viele Web-APIs, auf die JavaScript zugreifen kann, die asynchron sind, was bedeutet, dass sie parallel zu anderen Operationen ausgeführt werden können, anstatt sequenziell. Dies ist nützlich, weil es dem Benutzer ermöglicht, den Browser normal zu verwenden, während die asynchronen Operationen verarbeitet werden.
Als JavaScript-Entwickler müssen Sie wissen, wie Sie mit asynchronen Web-APIs arbeiten und auf die Antwort oder den Fehler dieser Operationen reagieren. In diesem Artikel erfahren Sie mehr über die Ereignisschleife, die ursprüngliche Methode zum Umgang mit asynchronem Verhalten durch Rückrufe, die aktualisierte ECMAScript 2015-Ergänzung von Versprechen und die moderne Praxis, async/await
zu verwenden.
Hinweis: Dieser Artikel konzentriert sich auf clientseitiges JavaScript in der Browserumgebung. Die gleichen Konzepte gelten im Allgemeinen auch in der Node.js-Umgebung, jedoch verwendet Node.js eigene C++-APIs anstelle der Web-APIs des Browsers. Für weitere Informationen zur asynchronen Programmierung in Node.js werfen Sie einen Blick auf How To Write Asynchronous Code in Node.js.
Die Ereignisschleife
Dieser Abschnitt wird erklären, wie JavaScript asynchronen Code mit der Ereignisschleife verarbeitet. Zunächst wird eine Demonstration der Arbeitsweise der Ereignisschleife durchgeführt und dann die beiden Elemente der Ereignisschleife erläutert: der Stapel und die Warteschlange.
JavaScript-Code, der keine asynchronen Web-APIs verwendet, wird synchron ausgeführt – einer nach dem anderen, sequenziell. Dies wird durch diesen Beispielcode demonstriert, der drei Funktionen aufruft, von denen jede eine Zahl in die Konsole druckt:
In diesem Code werden drei Funktionen definiert, die Zahlen mit console.log()
ausgeben.
Anschließend rufen Sie die Funktionen auf:
Die Ausgabe erfolgt basierend auf der Reihenfolge, in der die Funktionen aufgerufen wurden – erstens()
, zweitens()
und dann drittens()
:
Output1
2
3
Wenn eine asynchrone Web-API verwendet wird, werden die Regeln komplizierter. Eine integrierte API, mit der Sie dies testen können, ist setTimeout
, die einen Timer setzt und eine Aktion nach einer festgelegten Zeitspanne ausführt. setTimeout
muss asynchron sein, da sonst der gesamte Browser während des Wartens eingefroren bleiben würde, was zu einer schlechten Benutzererfahrung führen würde.
Fügen Sie setTimeout
zur Funktion zweitens
hinzu, um eine asynchrone Anfrage zu simulieren:
setTimeout
nimmt zwei Argumente: die Funktion, die es asynchron ausführen wird, und die Zeit, die es warten wird, bevor es diese Funktion aufruft. In diesem Code haben Sie console.log
in eine anonyme Funktion eingeschlossen und sie an setTimeout
übergeben, dann die Funktion so eingestellt, dass sie nach 0
Millisekunden ausgeführt wird.
Rufen Sie nun die Funktionen auf, wie Sie es zuvor getan haben:
Man könnte erwarten, dass bei einem setTimeout
von 0
das Ausführen dieser drei Funktionen immer noch dazu führt, dass die Zahlen in sequenzieller Reihenfolge gedruckt werden. Aber weil es asynchron ist, wird die Funktion mit dem Timeout zuletzt gedruckt:
Output1
3
2
Ob Sie den Timeout auf null Sekunden oder fünf Minuten setzen, macht keinen Unterschied – das von asynchronem Code aufgerufene console.log
wird nach den synchronen Top-Level-Funktionen ausgeführt. Dies geschieht, weil die JavaScript-Hostumgebung, in diesem Fall der Browser, ein Konzept namens die Ereignisschleife verwendet, um Parallelität oder parallele Ereignisse zu behandeln. Da JavaScript immer nur eine Anweisung gleichzeitig ausführen kann, muss die Ereignisschleife darüber informiert werden, wann welche spezifische Anweisung ausgeführt werden soll. Die Ereignisschleife behandelt dies mit den Konzepten eines Stapels und einer Warteschlange.
Stapel
Der Stack oder Aufrufstapel speichert den Zustand der aktuell ausgeführten Funktion. Wenn Sie mit dem Konzept eines Stapels nicht vertraut sind, können Sie ihn sich als Array mit „Last In, First Out“ (LIFO)-Eigenschaften vorstellen, was bedeutet, dass Sie nur Elemente am Ende des Stapels hinzufügen oder entfernen können. JavaScript führt den aktuellen Rahmen (oder Funktionsaufruf in einer bestimmten Umgebung) im Stapel aus, entfernt ihn und fährt mit dem nächsten fort.
Für das Beispiel, das nur synchronen Code enthält, behandelt der Browser die Ausführung in folgender Reihenfolge:
- Fügen Sie
first()
dem Stapel hinzu, führen Siefirst()
aus, das1
in die Konsole protokolliert, und entfernen Siefirst()
aus dem Stapel. - Fügen Sie
second()
dem Stapel hinzu, führen Siesecond()
aus, das2
in die Konsole protokolliert, und entfernen Siesecond()
aus dem Stapel. - Fügen Sie
third()
dem Stapel hinzu, führen Siethird()
aus, das3
in die Konsole protokolliert, und entfernen Siethird()
aus dem Stapel.
Das zweite Beispiel mit setTimeout
sieht so aus:
- Fügen Sie
first()
dem Stapel hinzu, führen Siefirst()
aus, das1
in die Konsole protokolliert, und entfernen Siefirst()
aus dem Stapel. - Fügen Sie
second()
dem Stapel hinzu, führen Siesecond()
aus.- Fügen Sie
setTimeout()
dem Stapel hinzu, führen Sie diesetTimeout()
Web-API aus, die einen Timer startet und die anonyme Funktion zur Warteschlange hinzufügt, und entfernen SiesetTimeout()
aus dem Stapel.
- Fügen Sie
- Entfernen Sie
second()
vom Stack. - Fügen Sie
third()
zum Stack hinzu, führen Siethird()
aus, das3
in die Konsole protokolliert, und entfernen Siethird()
vom Stack. - Die Ereignisschleife überprüft die Warteschlange auf ausstehende Nachrichten und findet die anonyme Funktion von
setTimeout()
, fügt die Funktion zum Stack hinzu, die2
in die Konsole protokolliert, und entfernt sie dann vom Stack.
Mit setTimeout
, einer asynchronen Web-API, wird das Konzept der Warteschlange eingeführt, das in diesem Tutorial als nächstes behandelt wird.
Warteschlange
Die Warteschlange, auch als Nachrichtenwarteschlange oder Aufgabenwarteschlange bezeichnet, ist ein Wartebereich für Funktionen. Wenn der Aufrufstapel leer ist, überprüft die Ereignisschleife die Warteschlange auf ausstehende Nachrichten, beginnend mit der ältesten Nachricht. Sobald sie eine findet, fügt sie sie dem Stack hinzu, der die Funktion in der Nachricht ausführt.
Im setTimeout
-Beispiel wird die anonyme Funktion sofort nach dem Rest der Ausführung auf oberster Ebene ausgeführt, da der Timer auf 0
Sekunden gesetzt wurde. Es ist wichtig zu bedenken, dass der Timer nicht bedeutet, dass der Code genau nach 0
Sekunden oder nach der angegebenen Zeit ausgeführt wird, sondern dass die anonyme Funktion nach dieser Zeitspanne zur Warteschlange hinzugefügt wird. Dieses Warteschlangensystem existiert, weil wenn der Timer die anonyme Funktion direkt auf den Stack hinzufügen würde, wenn der Timer endet, würde es die aktuell laufende Funktion unterbrechen, was unbeabsichtigte und unvorhersehbare Auswirkungen haben könnte.
Hinweis: Es gibt auch eine weitere Warteschlange namens Aufgabenwarteschlange oder Mikroaufgabenwarteschlange, die Promises behandelt. Mikroaufgaben wie Promises werden mit höherer Priorität behandelt als Makroaufgaben wie setTimeout
.
Jetzt wissen Sie, wie die Ereignisschleife den Stapel und die Warteschlange verwendet, um die Ausführungsreihenfolge des Codes zu behandeln. Die nächste Aufgabe besteht darin, herauszufinden, wie die Ausführungsreihenfolge im Code gesteuert werden kann. Dazu werden Sie zunächst die ursprüngliche Methode kennenlernen, um sicherzustellen, dass asynchroner Code von der Ereignisschleife korrekt behandelt wird: Callback-Funktionen.
Callback-Funktionen
Im Beispiel mit setTimeout
wurde die Funktion mit dem Timeout nach allem im Hauptausführungskontext ausgeführt. Aber wenn Sie sicherstellen möchten, dass eine der Funktionen, wie die Funktion dritte
, nach dem Timeout ausgeführt wird, müssen Sie asynchrone Codierungsmethoden verwenden. Der Timeout hier kann einen asynchronen API-Aufruf darstellen, der Daten enthält. Sie möchten mit den Daten vom API-Aufruf arbeiten, müssen aber sicherstellen, dass die Daten zuerst zurückgegeben werden.
Die ursprüngliche Lösung für dieses Problem besteht darin, Rückruffunktionen zu verwenden. Rückruffunktionen haben keine spezielle Syntax; sie sind nur eine Funktion, die als Argument an eine andere Funktion übergeben wurde. Die Funktion, die eine andere Funktion als Argument akzeptiert, wird als Höher-Ordnung-Funktion bezeichnet. Gemäß dieser Definition kann jede Funktion eine Rückruffunktion werden, wenn sie als Argument übergeben wird. Rückrufe sind von Natur aus nicht asynchron, können aber für asynchrone Zwecke verwendet werden.
Hier ist ein syntaktisches Codebeispiel einer Höher-Ordnung-Funktion und eines Rückrufs:
In diesem Code definieren Sie eine Funktion fn
, definieren eine Funktion höherOrdnungsFunktion
, die eine Funktion rückruf
als Argument akzeptiert, und übergeben fn
als Rückruf an höherOrdnungsFunktion
.
Die Ausführung dieses Codes wird Folgendes ergeben:
OutputJust a function
Kehren wir zu den ersten
, zweiten
und dritten
Funktionen mit setTimeout
zurück. Dies ist, was Sie bisher haben:
Die Aufgabe besteht darin, die Ausführung der dritten
Funktion immer zu verzögern, bis die asynchrone Aktion in der zweiten
Funktion abgeschlossen ist. Hier kommen Rückruf-Funktionen ins Spiel. Anstatt erste
, zweite
und dritte
auf der obersten Ebene der Ausführung auszuführen, werden Sie die dritte
Funktion als Argument an zweite
übergeben. Die zweite
Funktion wird den Rückruf nach Abschluss der asynchronen Aktion ausführen.
Hier sind die drei Funktionen mit einem Rückruf angewendet:
Jetzt führen Sie erste
und zweite
aus, und übergeben Sie dann dritte
als Argument an zweite
:
Nach Ausführung dieses Codeblocks erhalten Sie die folgende Ausgabe:
Output1
2
3
Zuerst wird 1
gedruckt, und nach Abschluss des Timers (in diesem Fall null Sekunden, aber Sie können ihn auf eine beliebige Menge ändern) wird 2
dann 3
gedruckt. Indem Sie eine Funktion als Rückruf übergeben, haben Sie die Ausführung der Funktion erfolgreich verzögert, bis die asynchrone Web-API (setTimeout
) abgeschlossen ist.
Der entscheidende Punkt hier ist, dass Rückruffunktionen nicht asynchron sind – setTimeout
ist die asynchrone Web-API, die für die Behandlung asynchroner Aufgaben verantwortlich ist. Der Rückruf ermöglicht es lediglich, informiert zu werden, wenn eine asynchrone Aufgabe abgeschlossen ist, und behandelt den Erfolg oder das Scheitern der Aufgabe.
Jetzt, da Sie gelernt haben, wie Sie Rückrufe verwenden, um asynchrone Aufgaben zu behandeln, erklärt der nächste Abschnitt die Probleme des Verschachtelns zu vieler Rückrufe und das Erstellen einer „Pyramide des Grauens“.
Verschachtelte Rückrufe und die Pyramide des Grauens
Rückruffunktionen sind eine effektive Möglichkeit, die verzögerte Ausführung einer Funktion zu gewährleisten, bis eine andere abgeschlossen ist und Daten zurückgibt. Aufgrund der verschachtelten Struktur von Rückrufen kann der Code jedoch unübersichtlich werden, wenn Sie viele aufeinanderfolgende asynchrone Anfragen haben, die voneinander abhängen. Dies war für JavaScript-Entwickler früher eine große Frustration, und daher wird Code, der verschachtelte Rückrufe enthält, oft als „Pyramide des Grauens“ oder „Rückrufhölle“ bezeichnet.
Hier ist eine Demonstration verschachtelter Rückrufe:
In diesem Code ist jedes neue setTimeout
in eine höhere Funktion verschachtelt, wodurch eine Pyramidenform mit immer tieferen Rückrufen entsteht. Das Ausführen dieses Codes würde Folgendes ergeben:
Output1
2
3
In der Praxis kann dies bei echtzeit Asynchroncode viel komplizierter werden. Wahrscheinlich müssen Sie Fehlerbehandlungen im asynchronen Code durchführen und dann Daten von jeder Antwort an die nächste Anfrage übergeben. Dies mit Rückrufen zu tun, wird Ihren Code schwer lesbar und wartbar machen.
Hier ist ein ausführbares Beispiel für einen realistischeren „Pyramiden-der-Verzweiflung“-Code, mit dem Sie herumspielen können:
In diesem Code müssen Sie jede Funktion auf eine mögliche response
und einen möglichen Fehler
vorbereiten, was die Funktion callbackHell
visuell verwirrend macht.
Das Ausführen dieses Codes ergibt Folgendes:
Output
First 9
Second 3
Error: Whoa! Something went wrong.
at asynchronousRequest (<anonymous>:4:21)
at second (<anonymous>:29:7)
at <anonymous>:9:13
Diese Art der Behandlung von asynchronem Code ist schwer nachvollziehbar. Daher wurde das Konzept der Versprechen in ES6 eingeführt. Dies ist der Schwerpunkt des nächsten Abschnitts.
Versprechen
A promise represents the completion of an asynchronous function. It is an object that might return a value in the future. It accomplishes the same basic goal as a callback function, but with many additional features and a more readable syntax. As a JavaScript developer, you will likely spend more time consuming promises than creating them, as it is usually asynchronous Web APIs that return a promise for the developer to consume. This tutorial will show you how to do both.
Erstellen eines Versprechens
Sie können ein Versprechen mit der Syntax new Promise
initialisieren, und Sie müssen es mit einer Funktion initialisieren. Die Funktion, die einem Versprechen übergeben wird, hat die Parameter resolve
und reject
. Die Funktionen resolve
und reject
behandeln jeweils den Erfolg und das Scheitern einer Operation.
Schreiben Sie die folgende Zeile, um ein Versprechen zu deklarieren:
Wenn Sie das initialisierte Versprechen in diesem Zustand mit der Konsole Ihres Webbrowsers inspizieren, werden Sie feststellen, dass es den Status ausstehend
und den Wert undefined
hat:
Output__proto__: Promise
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined
Bisher wurde nichts für das Versprechen eingerichtet, daher wird es für immer in einem ausstehenden
Zustand verbleiben. Das erste, was Sie tun können, um ein Versprechen zu testen, ist, das Versprechen zu erfüllen, indem Sie es mit einem Wert auflösen:
Wenn Sie das Versprechen nun inspizieren, werden Sie feststellen, dass es den Status erfüllt
hat und ein Wert
auf den von Ihnen an resolve
übergebenen Wert gesetzt ist:
Output__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: "We did it!"
Wie zu Beginn dieses Abschnitts angegeben, ist ein Versprechen ein Objekt, das einen Wert zurückgeben kann. Nach erfolgreicher Erfüllung wird der Wert
von undefined
auf mit Daten gefüllt geändert.
A promise can have three possible states: pending, fulfilled, and rejected.
- Ausstehend – Ausgangszustand vor Auflösung oder Ablehnung
- Erfüllt – Erfolgreiche Operation, Versprechen wurde erfüllt
- Ablehnen – Fehlgeschlagene Operation, Versprechen wurde abgelehnt
Nachdem ein Versprechen erfüllt oder abgelehnt wurde, ist es abgeschlossen.
Jetzt, da Sie eine Vorstellung davon haben, wie Versprechen erstellt werden, wollen wir uns ansehen, wie ein Entwickler diese Versprechen nutzen kann.
Versprechen nutzen
Das Versprechen im letzten Abschnitt wurde mit einem Wert erfüllt, aber Sie möchten auch auf den Wert zugreifen können. Versprechen haben eine Methode namens then
, die nach dem Erreichen von resolve
im Code ausgeführt wird. then
gibt den Wert des Versprechens als Parameter zurück.
So geben Sie den Wert
des Beispielversprechens zurück und protokollieren ihn:
Das von Ihnen erstellte Versprechen hatte einen [[PromiseValue]]
von Wir haben es geschafft!
. Dieser Wert wird als Reaktion
in die anonyme Funktion übergeben:
OutputWe did it!
Bisher war das von Ihnen erstellte Beispiel nicht mit einer asynchronen Web-API verbunden – es erklärte nur, wie man ein natives JavaScript-Versprechen erstellt, erfüllt und verbraucht. Mit setTimeout
können Sie eine asynchrone Anfrage testen.
Der folgende Code simuliert Daten, die von einer asynchronen Anfrage als Versprechen zurückgegeben werden:
Die Verwendung der Syntax then
stellt sicher, dass die response
nur protokolliert wird, wenn die setTimeout
-Operation nach 2000
Millisekunden abgeschlossen ist. All dies geschieht ohne Verschachtelung von Rückrufen.
Nun, nach zwei Sekunden, wird der Wert des Versprechens gelöst und im then
protokolliert:
OutputResolving an asynchronous request!
Versprechen können auch verkettet werden, um Daten an mehr als eine asynchrone Operation weiterzugeben. Wenn ein Wert in then
zurückgegeben wird, kann ein weiteres then
hinzugefügt werden, das mit dem Rückgabewert des vorherigen then
erfüllt wird:
Die erfüllte Antwort im zweiten then
wird den Rückgabewert protokollieren:
OutputResolving an asynchronous request! And chaining!
Da then
verkettet werden kann, ermöglicht es, dass die Verwendung von Versprechen synchroner erscheint als Rückrufe, da sie nicht verschachtelt werden müssen. Dies ermöglicht einen lesbareren Code, der einfacher gewartet und überprüft werden kann.
Fehlerbehandlung
Bisher haben Sie nur ein Versprechen mit einer erfolgreichen resolve
-Anweisung behandelt, die das Versprechen in einen erfüllten
Zustand versetzt. Häufig müssen Sie jedoch bei einer asynchronen Anfrage auch einen Fehler behandeln – wenn die API nicht verfügbar ist oder eine fehlerhafte oder nicht autorisierte Anfrage gesendet wird. Ein Versprechen sollte beide Fälle behandeln können. In diesem Abschnitt erstellen Sie eine Funktion, um sowohl den Erfolgs- als auch den Fehlerfall beim Erstellen und Verbrauchen eines Versprechens zu testen.
Diese getUsers
-Funktion wird ein Flag an ein Versprechen übergeben und das Versprechen zurückgeben:
Richten Sie den Code so ein, dass wenn onSuccess
true
ist, der Timeout mit einigen Daten erfüllt wird. Wenn false
, wird die Funktion mit einem Fehler abgelehnt:
Für das erfolgreiche Ergebnis geben Sie JavaScript-Objekte zurück, die Beispieldaten für Benutzer darstellen.
Um den Fehler zu behandeln, verwenden Sie die catch
-Methode. Dadurch erhalten Sie einen Fehler-Callback mit dem Fehler
als Parameter.
Führen Sie den Befehl getUser
mit onSuccess
auf false
gesetzt aus, indem Sie die then
-Methode für den Erfolgsfall und die catch
-Methode für den Fehler verwenden:
Seit der Fehler ausgelöst wurde, wird das then
übersprungen und das catch
wird den Fehler behandeln:
OutputFailed to fetch data!
Wenn Sie die Flagge umschalten und stattdessen resolve
, wird das catch
ignoriert und die Daten werden zurückgegeben:
Dies liefert die Benutzerdaten:
Output(3) [{…}, {…}, {…}]
0: {id: 1, name: "Jerry"}
1: {id: 2, name: "Elaine"}
3: {id: 3, name: "George"}
Zu Referenzzwecken hier eine Tabelle mit den Handler-Methoden für Promise
-Objekte:
Method | Description |
---|---|
then() |
Handles a resolve . Returns a promise, and calls onFulfilled function asynchronously |
catch() |
Handles a reject . Returns a promise, and calls onRejected function asynchronously |
finally() |
Called when a promise is settled. Returns a promise, and calls onFinally function asynchronously |
Versprechen können verwirrend sein, sowohl für neue Entwickler als auch für erfahrene Programmierer, die noch nie in einer asynchronen Umgebung gearbeitet haben. Wie bereits erwähnt, ist es jedoch viel häufiger, Versprechen zu konsumieren, als sie zu erstellen. Normalerweise wird ein Web-API des Browsers oder eine Bibliothek von Drittanbietern das Versprechen bereitstellen, und Sie müssen es nur konsumieren.
In der abschließenden Versprechen-Sektion wird dieses Tutorial einen häufigen Anwendungsfall einer Web-API, die Versprechen zurückgibt, zitieren: die Fetch-API.
Verwendung der Fetch-API mit Versprechen
Eine der nützlichsten und häufig verwendeten Web-APIs, die ein Versprechen zurückgibt, ist die Fetch-API, mit der Sie eine asynchrone Ressourcenanforderung über ein Netzwerk durchführen können. fetch
ist ein zweiteiliger Prozess und erfordert daher die Verkettung von then
. Dieses Beispiel zeigt, wie Sie die GitHub-API aufrufen, um die Daten eines Benutzers abzurufen, und gleichzeitig eventuelle Fehler behandeln:
Die fetch
-Anfrage wird an die URL https://api.github.com/users/octocat
gesendet, die asynchron auf eine Antwort wartet. Das erste then
übergibt die Antwort an eine anonyme Funktion, die die Antwort als JSON-Daten formatiert, und übergibt das JSON an ein zweites then
, das die Daten in die Konsole protokolliert. Das catch
-Statement protokolliert etwaige Fehler in die Konsole.
Die Ausführung dieses Codes ergibt folgendes Ergebnis:
Outputlogin: "octocat",
id: 583231,
avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4"
blog: "https://github.blog"
company: "@github"
followers: 3203
...
Dies sind die angeforderten Daten von https://api.github.com/users/octocat
, dargestellt im JSON-Format.
Dieser Abschnitt des Tutorials zeigt, dass Promises viele Verbesserungen für den Umgang mit asynchronem Code bieten. Aber während die Verwendung von then
zur Behandlung asynchroner Aktionen einfacher zu verfolgen ist als die Pyramide von Rückrufen, bevorzugen einige Entwickler immer noch ein synchrones Format zum Schreiben asynchronen Codes. Um diesem Bedürfnis gerecht zu werden, hat ECMAScript 2016 (ES7) die Verwendung von async
-Funktionen und des await
-Schlüsselworts eingeführt, um die Arbeit mit Promises zu erleichtern.
Asynchrone Funktionen mit async/await
Eine async
-Funktion ermöglicht es Ihnen, asynchronen Code auf eine Art und Weise zu behandeln, die synchron erscheint. async
-Funktionen verwenden immer noch Promises im Hintergrund, haben jedoch eine traditionellere JavaScript-Syntax. In diesem Abschnitt werden Sie Beispiele für diese Syntax ausprobieren.
Sie können eine async
-Funktion erstellen, indem Sie das async
-Schlüsselwort vor eine Funktion setzen:
Obwohl diese Funktion noch nichts Asynchrones behandelt, verhält sie sich anders als eine traditionelle Funktion. Wenn Sie die Funktion ausführen, werden Sie feststellen, dass sie ein Promise mit einem [[PromiseStatus]]
und [[PromiseValue]]
anstelle eines Rückgabewerts zurückgibt.
Probieren Sie dies aus, indem Sie einen Aufruf der getUser
-Funktion protokollieren:
Dies wird Folgendes liefern:
Output__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: Object
Dies bedeutet, dass Sie eine async
-Funktion mit then
genauso behandeln können, wie Sie ein Promise behandeln könnten. Probieren Sie dies mit folgendem Code aus:
Dieser Aufruf von getUser
übergibt den Rückgabewert an eine anonyme Funktion, die den Wert in die Konsole protokolliert.
Sie erhalten Folgendes, wenn Sie dieses Programm ausführen:
Output{}
Eine async
-Funktion kann ein innerhalb von ihr aufgerufenes Versprechen mit dem await
-Operator verarbeiten. await
kann innerhalb einer async
-Funktion verwendet werden und wartet, bis sich ein Versprechen erfüllt hat, bevor der bestimmte Code ausgeführt wird.
Mit diesem Wissen können Sie die Fetch-Anforderung aus dem letzten Abschnitt mit async
/await
wie folgt neu schreiben:
Die await
-Operatoren hier stellen sicher, dass die data
nicht protokolliert wird, bevor die Anforderung sie mit Daten gefüllt hat.
Jetzt kann die endgültige data
innerhalb der getUser
-Funktion behandelt werden, ohne dass then
verwendet werden muss. Dies ist die Ausgabe des Protokollierens von data
:
Outputlogin: "octocat",
id: 583231,
avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4"
blog: "https://github.blog"
company: "@github"
followers: 3203
...
Hinweis: In vielen Umgebungen ist async
erforderlich, um await
zu verwenden. Einige neue Versionen von Browsern und Node erlauben jedoch die Verwendung von Top-Level-await
, was es Ihnen ermöglicht, das Erstellen einer async-Funktion zu umgehen, um das await
einzuschließen.
Zuletzt, da Sie das erfüllte Versprechen innerhalb der asynchronen Funktion verarbeiten, können Sie auch den Fehler innerhalb der Funktion behandeln. Anstelle der Verwendung der catch
-Methode mit then
verwenden Sie das try
/catch
-Muster, um die Ausnahme zu behandeln.
Fügen Sie den folgenden markierten Code hinzu:
Das Programm wird nun zum catch
-Block springen, wenn es einen Fehler erhält, und diesen Fehler in die Konsole protokollieren.
Moderne asynchrone JavaScript-Code wird meistens mit der Syntax async
/await
behandelt, aber es ist wichtig, ein Grundwissen darüber zu haben, wie Promises funktionieren, insbesondere da Promises zusätzliche Funktionen bieten können, die nicht mit async
/await
behandelt werden können, wie das Kombinieren von Promises mit Promise.all()
.
Hinweis: async
/await
kann durch die Verwendung von Generatoren in Kombination mit Promises reproduziert werden, um Ihrem Code mehr Flexibilität zu verleihen. Um mehr zu erfahren, schauen Sie sich unser Tutorial Understanding Generators in JavaScript an.
Fazit
Weil Web-APIs oft Daten asynchron bereitstellen, ist das Erlernen des Umgangs mit dem Ergebnis asynchroner Aktionen ein wesentlicher Bestandteil der Arbeit als JavaScript-Entwickler. In diesem Artikel haben Sie gelernt, wie die Host-Umgebung die Ereignisschleife verwendet, um die Reihenfolge der Codeausführung mit dem Stack und der Warteschlange zu verwalten. Sie haben auch Beispiele für drei Möglichkeiten ausprobiert, den Erfolg oder das Scheitern eines asynchronen Ereignisses zu behandeln, mit Callbacks, Promises und der async
/await
-Syntax. Schließlich haben Sie die Fetch Web-API verwendet, um asynchrone Aktionen zu behandeln.
Für weitere Informationen darüber, wie der Browser parallele Ereignisse behandelt, lesen Sie Concurrency-Modell und die Ereignisschleife auf dem Mozilla Developer Network. Wenn Sie mehr über JavaScript erfahren möchten, kehren Sie zu unserer How To Code in JavaScript-Serie zurück.