Geschrieben von

JavaScript: async/await

WebDev

In JavaScript müssen wir oft asynchrone Aktionen durchführen. JavaScript verwendet einen Event-Loop, um einen Code auszuführen, während ein anderer Code läuft. Ursprünglich wurden Callback-Funktionen eingesetzt, um asynchrone Abläufe zu steuern. Mit ES6 wurden aber Promises eingeführt. Mit ES8 wird eine neue Syntax für asynchrone Aktionen eingeführt: async…await. Dabei handelt es sich um einen syntaktischen Zucker.

Das async Keyword

Mit async werden Funktionen geschrieben, die eine asynchrone Aktion verarbeiten sollen. Das async Keyword wird der Funktionsdeklaration vorangestellt; danach wird die Funktion aufgerufen:

async function myFunction() {
  // Hier kommt die Logik
};
 
myFunction();

Alternativ kann man auch Arrow Functions verwenden:

const myFunction = async () => {
  // Hier kommt die Logik
};
 
myFunction();

Da async-Funktionen immer ein Promise zurückgeben, können hierbei Methoden wie then() oder catch() angehangen werden. Das heißt:

  • Wenn die Funktion nichts zurückgibt, dann gibt die async-Funktion auch undefined zurück.
  • Wird ein Promise zurückgegeben, dann wird dieses auch von der async-Funktion direkt zurückgegeben.
  • Wird ein Wert zurückgegeben, der nicht einem Promise entspricht, dann wandelt die async-Funktionen diesen Wert in ein Promise um.

Mit dem async-Keyword allein kommt man aber nicht weit. async wird immer zusammen mit await genutzt.

Der await-Operator

Der await-Operator kann grundsätzlich immer nur innerhalb einer async-Funktion verwendet werden. await gibt dabei den aufgelösten Wert eines Promise zurück. Das Grundprinzip von await lautet wie folgt:

Da ein Promise irgendwann in Zukunft aufgelöst wird (Zeitpunkt also unbekannt), unterbricht bzw. pausiert der await-Operator die Ausführung der async-Funktion (also asynchronen Funktion) bis ein bestimmter Promise-Zustand eintritt.

Ein Beispiel. Gehen wir davon aus wir haben eine Funktion, die ein Promise zurück gibt:

const sampleCar = () => {
  return new Promise((resolve, reject) => {
    resolve('Ich bin ein Auto');
    });
};

Mit einer Funktion, die das Promise auf klassischer Weise auflöst, sieht das wie folgt aus:

function getCar() {
  sampleCar().then((car) => {
    console.log(car);
  });
};
 
getCar(); // Funktion ausführen
 
"Ich bin ein Auto" // Ausgabe

Mit der async/await-Variante sieht das wie folgt aus:

async function sayCar() {
  let myCar = await sampleCar();
  console.log(myCar);
};
 
sayCar(); // Funktion ausführen
 
"Ich bin ein Auto" // Ausgabe

Wie man sehen kann wird innerhalb der async-Funktion das Promise in eine Variable gepackt. Mit dem vorangestellten await-Operator hält man den Promise so lange zurück, bis dieser aufgelöst wurde. Die Variable, in der das Promise ist, wird dann mittels console.log ausgegeben. Wie wichtig der await-Operator ist, sehen wir auch am nachfolgendem Beispiel.

Wir erstellen zunächst eine Variable myPromise, die ein Promise zurückgibt. Der aufgelöste Wert wird jedoch mit eine Verzögerung von 1 Sekunde zurückgegeben (mittels der setTimeout-Funktion):

let myPromise = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Hallo, ich wurde aufgelöst')
    }, 1000);
  });
}

Nun erstellen wir 2 async-Funktionen zur Demonstration. Die erste async-Funktion bekommt keinen await-Operator, die Zweite schon:

async function withoutAwait() {
  let value = myPromise();
  console.log(value);
}
 
async function withAwait() {
  let value = await myPromise();
  console.log(value);
}

Was würde nun passieren, wenn wir die erste Funktion aufrufen? Da kein await-Operator vorhanden ist, der das Promise so lange zurückhält, bis dieser aufgelöst wurde, wird nur das Promise-Objekt mit dem entsprechenden Status zurückgegeben:

withoutAwait(); // Liefert Promise { <pending> } zurück

Rufen wir hingegen die async-Funktion mit dem await-Operator bekommen wir das gewünschte Ergebnis:

withAwait(); // Liefert "Hallo, ich wurde aufgelöst" nach 1 Sekunde zurück