Understanding Async Await

Avatar of Sarah Drasner
Sarah Drasner am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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 &#x1f32e;s
> Here are your &#x1f32e;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.