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:
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