Beim Schreiben von Code für das Web werden Sie irgendwann einen Prozess durchführen müssen, dessen Abschluss einige Momente dauern kann. JavaScript kann nicht wirklich Multitasking betreiben, daher benötigen wir eine Möglichkeit, diese langlaufenden Prozesse zu handhaben.
Async/Await ist eine Methode, um diese Art von zeitbasierter Abfolge zu handhaben. Es ist besonders nützlich, wenn Sie eine Netzwerkanfrage stellen und dann mit den resultierenden Daten arbeiten müssen. Tauchen wir ein!
Versprechen? Versprechen.
Async/Await ist eine Art von Promise. Promises in JavaScript sind Objekte, die mehrere Zustände haben können (ähnlich wie im echten Leben ☺️). Promises tun dies, weil manchmal das, wonach wir fragen, nicht sofort verfügbar ist, und wir müssen erkennen können, in welchem Zustand es sich befindet.
Stellen Sie sich vor, jemand bittet Sie, ihm zu versprechen, etwas für ihn zu tun, z. B. ihm beim Umzug zu helfen. Es gibt den Anfangszustand, in dem sie gefragt haben. Aber Sie haben Ihr Versprechen noch nicht erfüllt, bis Sie auftauchen und ihm beim Umzug helfen. Wenn Sie Ihre Pläne absagen, haben Sie das Versprechen abgelehnt.
Ähnlich sind die drei möglichen Zustände für ein Promise in JavaScript:
- ausstehend (pending): wenn Sie ein Promise zum ersten Mal aufrufen und noch unbekannt ist, was es zurückgeben wird.
- erfüllt (fulfilled): was bedeutet, dass die Operation erfolgreich abgeschlossen wurde
- abgelehnt (rejected): die Operation ist fehlgeschlagen
Hier ist ein Beispiel für ein Promise in diesen Zuständen
Hier ist der erfüllte Zustand. Wir speichern ein Promise namens getSomeTacos und übergeben die Parameter resolve und reject. Wir sagen dem Promise, dass es aufgelöst wurde, und das ermöglicht es uns, noch zweimal zu loggen.
const getSomeTacos = new Promise((resolve, reject) => {
console.log("Initial state: Excuse me can I have some tacos");
resolve();
})
.then(() => {
console.log("Order some tacos");
})
.then(() => {
console.log("Here are your tacos");
})
.catch(err => {
console.error("Nope! No tacos for you.");
});
> Initial state: Excuse me can I have some tacos
> Order some tacos
> Here are your tacos
Siehe den Pen
Promise States von Sarah Drasner (@sdras)
auf CodePen.
Wenn wir den abgelehnten Zustand wählen, führen wir dieselbe Funktion aus, lehnen sie aber dieses Mal ab. Was nun in der Konsole ausgegeben wird, ist der Anfangszustand und der Catch-Fehler.
const getSomeTacos = new Promise((resolve, reject) => {
console.log("Initial state: Excuse me can I have some tacos");
reject();
})
.then(() => {
console.log("Order some tacos");
})
.then(() => {
console.log("Here are your tacos");
})
.catch(err => {
console.error("Nope! No tacos for you.");
});
> Initial state: Excuse me can I have some tacos
> Nope! No tacos for you.
Und wenn wir den ausstehenden Zustand wählen, loggen wir einfach console.log, was wir gespeichert haben, getSomeTacos. Dies gibt einen ausstehenden Zustand aus, da dies der Zustand ist, in dem sich das Promise befindet, als wir es geloggt haben!
console.log(getSomeTacos)
> Initial state: Excuse me can I have some 🌮s
> Promise {<pending>}
> Order some 🌮s
> Here are your 🌮s
Und dann?
Aber hier ist ein Teil, der mich anfangs verwirrt hat. Um einen Wert aus einem Promise zu erhalten, müssen Sie .then() oder etwas anderes verwenden, das die Auflösung Ihres Promises zurückgibt. Das ergibt Sinn, wenn Sie darüber nachdenken, denn Sie müssen erfassen, was es schließlich sein wird – und nicht, was es anfangs ist –, da es sich anfangs in diesem ausstehenden Zustand befinden wird. Deshalb haben wir es oben beim Loggen des Promises als Promise {<pending>} ausgeben sehen. Zu diesem Zeitpunkt der Ausführung war noch nichts aufgelöst.
Async/Await ist wirklich nur syntaktischer Zucker auf Basis dieser Promises, die Sie gerade gesehen haben. Hier ist ein kleines Beispiel, wie ich es zusammen mit einem Promise verwenden könnte, um mehrere Ausführungen zu planen.
async function tacos() {
return await Promise.resolve("Now and then I get to eat delicious tacos!")
};
tacos().then(console.log)
Oder ein detaillierteres Beispiel
// this is the function we want to schedule. it's a promise.
const addOne = (x) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`I added one! Now it's ${x + 1}.`)
resolve()
}, 2000);
})
}
// we will immediately log the first one,
// then the addOne promise will run, taking 2 seconds
// then the final console.log will fire
async function addAsync() {
console.log('I have 10')
await addOne(10)
console.log(`Now I'm done!`)
}
addAsync()
> I have 10
> I added one! Now it's 11.
> Now I'm done!
Siehe den Pen
Async Example 1 von Sarah Drasner (@sdras)
auf CodePen.
Eine Sache (wartet) auf eine andere
Eine häufige Verwendung von Async/Await ist die Verknüpfung mehrerer asynchroner Aufrufe. Hier holen wir ein JSON, das wir für unseren nächsten Fetch-Aufruf verwenden, um herauszufinden, welche Art von Sache wir aus der zweiten API abrufen wollen. In unserem Fall möchten wir auf einige Programmierwitze zugreifen, aber zuerst müssen wir von einer anderen API erfahren, welche Art von Zitat wir wollen.
Die erste JSON-Datei sieht so aus – wir möchten, dass der Zitattyp zufällig ist.
{
"type": "random"
}
Die zweite API gibt etwas zurück, das wie folgt aussieht, gegeben den gerade erhaltenen Query-Parameter random.
{
"_id":"5a933f6f8e7b510004cba4c2",
"en":"For all its power, the computer is a harsh taskmaster. Its programs must be correct, and what we wish to say must be said accurately in every detail.",
"author":"Alan Perlis",
"id":"5a933f6f8e7b510004cba4c2"
}
Wir rufen die async Funktion auf und lassen sie dann warten, um die erste .json Datei abzurufen, bevor sie Daten von der API abruft. Sobald das passiert ist, können wir etwas mit dieser Antwort tun, z. B. sie auf unserer Seite hinzufügen.
async function getQuote() {
// get the type of quote from one fetch call, everything else waits for this to finish
let quoteTypeResponse = await fetch(`https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/quotes.json`)
let quoteType = await quoteTypeResponse.json()
// use what we got from the first call in the second call to an API, everything else waits for this to finish
let quoteResponse = await fetch("https://programming-quotes-api.herokuapp.com/quotes/" + quoteType.type)
let quote = await quoteResponse.json()
// finish up
console.log('done')
}
Wir können dies sogar vereinfachen, indem wir Template-Literale und Pfeilfunktionen verwenden.
async function getQuote() {
// get the type of quote from one fetch call, everything else waits for this to finish
let quoteType = await fetch(`quotes.json`).then(res => res.json())
// use what we got from the first call in the second call to an API, everything else waits for this to finish
let quote = await fetch(`programming-quotes.com/${quoteType.type}`).then(res => res.json())
// finish up
console.log('done')
}
getQuote()
Hier ist eine animierte Erklärung dieses Prozesses.
Siehe den Pen
Animated Description of Async Await von Sarah Drasner (@sdras)
auf CodePen.
Try, Catch, Finally
Schließlich möchten wir diesem Prozess Fehlerzustände hinzufügen. Dafür haben wir praktische try-, catch- und finally-Blöcke.
try {
// I’ll try to execute some code for you
}
catch(error) {
// I’ll handle any errors in that process
}
finally {
// I’ll fire either way
}
Lassen Sie uns den obigen Code umstrukturieren, um diese Syntax zu verwenden und Fehler abzufangen.
async function getQuote() {
try {
// get the type of quote from one fetch call, everything else waits for this to finish
let quoteType = await fetch(`quotes.json`).then(res => res.json())
// use what we got from the first call in the second call to an API, everything else waits for this to finish
let quote = await fetch(`programming-quotes.com/${quoteType.type}`).then(res => res.json())
// finish up
console.log('done')
}
catch(error) {
console.warn(`We have an error here: ${error}`)
}
}
getQuote()
Wir haben finally hier nicht verwendet, weil wir es nicht immer brauchen. Es ist ein Block, der immer ausgeführt wird, unabhängig davon, ob er erfolgreich ist oder fehlschlägt. Erwägen Sie die Verwendung von finally, wann immer Sie Dinge sowohl in try als auch in catch duplizieren. Ich verwende dies normalerweise für einige Bereinigungen. Ich habe einen Artikel darüber geschrieben, wenn Sie mehr wissen möchten.
Sie möchten möglicherweise irgendwann eine ausgefeiltere Fehlerbehandlung, z. B. eine Möglichkeit, eine asynchrone Funktion abzubrechen. Dafür gibt es leider keine native Möglichkeit, aber glücklicherweise hat Kyle Simpson eine Bibliothek namens CAF erstellt, die helfen kann.
Weitere Lektüre
Es ist üblich, dass Erklärungen zu Async/Await mit Callbacks, dann mit Promises beginnen und diese Erklärungen verwenden, um Async/Await zu rahmen. Da Async/Await heutzutage gut unterstützt wird, haben wir nicht alle diese Schritte durchlaufen. Es ist immer noch ein ziemlich guter Hintergrund, besonders wenn Sie ältere Codebasen pflegen müssen. Hier sind einige meiner Lieblingsressourcen.
- Async JavaScript: From Callbacks, to Promises, to Async/Await (Tyler McGinnis)
- Asynchronous JavaScript with async/await (Marius Schulz)
- Mastering Async JavaScript (James K. Nelson)
Danke – wie immer ein toller Beitrag, der wichtige Teile der Frontend-Entwicklung sehr klar erklärt :-)
Das wird etwas undurchsichtig sein, da ich keine Erklärung zum Nachschlagen hatte… aber…
Ich habe einmal eine ganze Gruppe von Programmierern gefragt, ob man einen Try-Catch-Block mit Async/Await verwenden kann, und eine große Mehrheit von ihnen sagte: Wow! Tun Sie das nicht. Das wollen Sie nicht.
Ich konnte nicht nachfragen, warum, weil die Präsentation damals weiterlief.
Gibt es einen Grund, warum die Verwendung von Try-Catch nachteilig wäre?
Es klingt verrückt, denn in einer Produktions-App müssen Sie diese potenziellen Fehler behandeln.
Hallo Dan!
Die Verwendung von
try…catchum einawaitist absolut in Ordnung und entspricht dem beabsichtigten Verhalten des Sprachfeatures. Genau deshalb wirftawaitbei einem abgelehnten Promise lokal den Fehler. Wie immer ist die *eigentliche* Frage: Haben Sie tatsächlich eine relevante Fehlerbehandlung, die lokal durchgeführt werden kann, oder sollten Sie den Fehler an den Aufrufer-Stack weitergeben?Das ist genau der Grund, warum
return awaitnur innerhalb einestry…catchals angemessen gilt: Wenn Sie eine Ablehnung lokal behandeln möchten, dann macht es Sinn,awaitzu verwenden. Andernfalls istreturn awaitunnötig, da sowieso kein zusätzlicher lokaler Code ausgeführt werden muss, sodass Sie genauso gut das ursprüngliche Promise zurückgeben können, ohne es in eine zusätzliche Ebene zu wickeln, die durch dieasyncFunktion Ihrer Funktion erstellt wird.Das einzige
return awaitim Code dieses Artikels fällt in diese Falle: Die Funktion könnte genauso gut nichtawaiten und nichtasyncsein, sie würde ein Promise anstelle von zwei verwenden und wäre ein kleines bisschen performanter... und mit weniger Code ;-)Viele Leute ziehen es vor, es beim Aufrufer zu verwenden. Z. B. iAmAsyncAndCouldThrow.catch(error => …)
Async/await und Promises sind so viel bequemer als verschachtelte Callbacks, aber ich verliere manchmal noch den Überblick bei verschachtelten Async-Funktionen und Schleifen. Das passiert mir meistens bei mehreren API-Aufrufen und Datenaufbereitung.
Liebe das! Habe es zu React Native Now #58 hinzugefügt.
https://reactnativenow.com/issues/58#start
Sarah,
Ausgezeichneter Artikel. Er hat mir geholfen, Promises / Async / Await besser zu verstehen.
Ich habe jedoch ein paar Fehler in den Codebeispielen gefunden, die dazu führten, dass sie nicht funktionierten.
In getQuote(): (die komprimierte Version),
Die Fetches verweisen auf relative Pfade.
fetch(
quotes.json)fetch(
programming-quotes.com/${quoteType.type})Als ich es zu den vollständigen Pfaden geändert habe, funktionierten sie.
fetch(
https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/quotes.json`)https://programming-quotes-api.herokuapp.com/quotes/${quoteType.type}`)fetch(