Async/Await ist eine syntaktische Erweiterung, die mit ECMAScript 2017 (ES8) eingeführt wurde und die asynchrone Programmierung in JavaScript auf eine neue Ebene hebt. Diese Funktionalität baut auf Promises auf, bietet jedoch eine intuitivere, synchron anmutende Syntax, die asynchronen Code lesbarer und wartbarer macht.
Async/Await besteht aus zwei Schlüsselwörtern:
async: Deklariert eine asynchrone
Funktion, die automatisch ein Promise zurückgibtawait: Pausiert die Ausführung der
async-Funktion bis ein Promise erfüllt ist// Eine einfache asynchrone Funktion
async function holeDaten() {
// Die Ausführung pausiert hier, bis das Promise erfüllt ist
const antwort = await fetch('https://api.beispiel.de/daten');
const daten = await antwort.json();
return daten; // Wird automatisch in ein Promise verpackt
}
// Verwendung der asynchronen Funktion
holeDaten()
.then(daten => {
console.log('Daten empfangen:', daten);
})
.catch(fehler => {
console.error('Fehler beim Abrufen der Daten:', fehler);
});Async/Await bietet mehrere Vorteile gegenüber der direkten Verwendung von Promises:
Vergleichen wir denselben Code mit Promises und mit Async/Await:
// Mit Promises
function holeDatenMitPromises() {
return fetch('https://api.beispiel.de/benutzer')
.then(antwort => antwort.json())
.then(benutzer => {
return fetch(`https://api.beispiel.de/posts?benutzerId=${benutzer.id}`)
.then(antwort => antwort.json())
.then(posts => {
return { benutzer, posts };
});
});
}
// Mit Async/Await
async function holeDatenMitAsyncAwait() {
const benutzerAntwort = await fetch('https://api.beispiel.de/benutzer');
const benutzer = await benutzerAntwort.json();
const postsAntwort = await fetch(`https://api.beispiel.de/posts?benutzerId=${benutzer.id}`);
const posts = await postsAntwort.json();
return { benutzer, posts };
}Der Async/Await-Code ist flacher, sequentieller und ähnelt strukturell synchronem Code, wodurch er einfacher zu lesen und zu verstehen ist.
Eine der größten Stärken von Async/Await ist die Möglichkeit, den bekannten try/catch-Mechanismus für die Fehlerbehandlung zu verwenden:
async function benutzerDatenLaden(benutzerId) {
try {
const antwort = await fetch(`https://api.beispiel.de/benutzer/${benutzerId}`);
if (!antwort.ok) {
throw new Error(`HTTP-Fehler: ${antwort.status}`);
}
const benutzer = await antwort.json();
console.log('Benutzer geladen:', benutzer);
return benutzer;
} catch (fehler) {
console.error('Fehler beim Laden des Benutzers:', fehler);
// Fehlerbehandlung
throw fehler; // Optional: Fehler weiterleiten
} finally {
console.log('Benutzerladeprozess abgeschlossen');
// Aufräumarbeiten durchführen
}
}Dies ist besonders nützlich, wenn mehrere asynchrone Operationen
ausgeführt werden, da ein einzelner try/catch-Block alle Fehler abfangen
kann, ohne dass mehrere .catch()-Aufrufe benötigt
werden.
async function sequentielleVerarbeitung(benutzerIds) {
const ergebnisse = [];
for (const id of benutzerIds) {
// Jeder Benutzer wird nacheinander geladen
const benutzer = await benutzerDatenLaden(id);
ergebnisse.push(benutzer);
}
return ergebnisse;
}async function paralleleVerarbeitung(benutzerIds) {
// Promise.all mit Map und async/await kombinieren
const promises = benutzerIds.map(id => benutzerDatenLaden(id));
// Alle Promises parallel ausführen
const ergebnisse = await Promise.all(promises);
return ergebnisse;
}
// Oder kürzer geschrieben:
async function paralleleVerarbeitungKurz(benutzerIds) {
return Promise.all(benutzerIds.map(id => benutzerDatenLaden(id)));
}Manchmal möchten wir die Anzahl gleichzeitiger Operationen begrenzen:
async function begrenzteParalleleVerarbeitung(benutzerIds, limit = 3) {
const ergebnisse = [];
// Array in Gruppen der Größe "limit" aufteilen
for (let i = 0; i < benutzerIds.length; i += limit) {
const gruppe = benutzerIds.slice(i, i + limit);
// Diese Gruppe parallel verarbeiten
const gruppenErgebnisse = await Promise.all(
gruppe.map(id => benutzerDatenLaden(id))
);
ergebnisse.push(...gruppenErgebnisse);
}
return ergebnisse;
}Async/Await kann in verschiedenen Funktionskontexten verwendet werden:
// Reguläre Funktion
async function regulaereFunktion() {
return await fetch('https://api.beispiel.de/daten');
}
// Funktionsausdruck
const funktionsAusdruck = async function() {
return await fetch('https://api.beispiel.de/daten');
};
// Arrow-Funktion
const arrowFunktion = async () => {
return await fetch('https://api.beispiel.de/daten');
};
// Methode in einer Klasse
class DatenService {
async holeDaten() {
return await fetch('https://api.beispiel.de/daten');
}
// Auch in Getter/Setter (aber selten verwendet)
async get daten() {
return await fetch('https://api.beispiel.de/daten');
}
}
// In einer IIFE (Immediately Invoked Function Expression)
(async () => {
try {
const daten = await fetch('https://api.beispiel.de/daten');
console.log(await daten.json());
} catch (fehler) {
console.error(fehler);
}
})();Mit ECMAScript 2022 wurde “Top-Level Await” eingeführt, das die
Verwendung von await außerhalb von async-Funktionen in
ES-Modulen ermöglicht:
// In einem ES-Modul, kein async erforderlich
const antwort = await fetch('https://api.beispiel.de/konfiguration');
const config = await antwort.json();
export const API_KEY = config.apiKey;
export const API_URL = config.apiUrl;
// Abhängigkeiten können auf die Auflösung warten
console.log('Konfiguration geladen');Dies ist besonders nützlich für: - Initialisierungscode in Modulen - Dynamischen Import von Modulen - Ressourcenladen beim Start - Konfigurationsabhängige Exporte
Beachten Sie, dass Top-Level Await nur in ES-Modulen (mit
type="module" oder .mjs-Dateien) und nicht in
CommonJS-Modulen oder Skripten funktioniert.
async function ladeUndVerarbeiteDaten() {
try {
// Daten laden
const antwort = await fetch('https://api.beispiel.de/daten');
const rohDaten = await antwort.json();
// Daten verarbeiten
const verarbeitet = rohDaten.map(item => ({
id: item.id,
name: item.name.toUpperCase(),
wert: item.wert * 2
}));
// Ergebnis zurückgeben
return verarbeitet;
} catch (fehler) {
console.error('Fehler bei der Datenverarbeitung:', fehler);
return []; // Fallback-Wert
}
}async function intelligenteDatenBeschaffung(id, { useCache = true } = {}) {
// Prüfen, ob Daten im Cache sind
if (useCache) {
try {
const cachedData = await ladeAusCache(id);
if (cachedData) {
console.log('Daten aus Cache geladen');
return cachedData;
}
} catch (cacheError) {
console.warn('Cache-Zugriff fehlgeschlagen:', cacheError);
// Cache-Fehler ignorieren und fortfahren
}
}
// Wenn nicht im Cache oder Cache deaktiviert, vom Server laden
try {
console.log('Lade Daten vom Server...');
const serverData = await ladeVomServer(id);
// Im Cache speichern für zukünftige Anfragen
if (useCache) {
await speichereImCache(id, serverData);
}
return serverData;
} catch (serverError) {
console.error('Serverzugriff fehlgeschlagen:', serverError);
throw serverError;
}
}async function mitWiederholung(funktion, { versuche = 3, verzoegerung = 1000 } = {}) {
let letzterFehler;
for (let versuch = 1; versuch <= versuche; versuch++) {
try {
return await funktion();
} catch (fehler) {
letzterFehler = fehler;
console.warn(`Versuch ${versuch}/${versuche} fehlgeschlagen:`, fehler);
if (versuch < versuche) {
// Warte vor dem nächsten Versuch
// Exponentielle Backoff-Strategie
const wartezeitMs = verzoegerung * Math.pow(2, versuch - 1);
console.log(`Warte ${wartezeitMs}ms vor dem nächsten Versuch...`);
await new Promise(resolve => setTimeout(resolve, wartezeitMs));
}
}
}
throw new Error(`Nach ${versuche} Versuchen fehlgeschlagen: ${letzterFehler}`);
}
// Verwendung
async function datenLaden() {
const daten = await mitWiederholung(
() => fetch('https://api.beispiel.de/unstable-endpoint').then(r => r.json()),
{ versuche: 5, verzoegerung: 500 }
);
console.log('Daten erfolgreich geladen:', daten);
}async function ladeDatenMitTimeout(url, timeoutMs = 5000) {
// AbortController erstellen
const controller = new AbortController();
const { signal } = controller;
// Timeout-Promise erstellen
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Zeitüberschreitung nach ${timeoutMs}ms`));
}, timeoutMs);
});
try {
// Race zwischen Fetch und Timeout
const antwort = await Promise.race([
fetch(url, { signal }),
timeoutPromise
]);
return await antwort.json();
} catch (fehler) {
if (fehler.name === 'AbortError') {
throw new Error('Die Anfrage wurde abgebrochen');
}
throw fehler;
}
}Mit ECMAScript 2018 wurden asynchrone Iteratoren eingeführt, die gut mit Async/Await zusammenarbeiten:
async function* generiereDaten() {
let id = 1;
while (id <= 5) {
// Simuliere asynchrone Operationen
await new Promise(resolve => setTimeout(resolve, 1000));
yield { id, wert: `Wert ${id}` };
id++;
}
}
async function verarbeiteDatenStream() {
const generator = generiereDaten();
for await (const daten of generator) {
console.log('Verarbeite:', daten);
// Weitere Verarbeitung...
}
console.log('Alle Daten verarbeitet');
}Async-Funktionen können als Event-Handler verwendet werden, wobei Vorsicht geboten ist, da die Rückgabewerte (Promises) nicht vom Event-System verarbeitet werden:
document.getElementById('submit-button').addEventListener('click', async (event) => {
event.preventDefault();
try {
const formDaten = new FormData(document.getElementById('mein-formular'));
const antwort = await fetch('/api/submit', {
method: 'POST',
body: formDaten
});
if (!antwort.ok) {
throw new Error(`Serverfehler: ${antwort.status}`);
}
const ergebnis = await antwort.json();
zeigeBestaetigungsMeldung(`Formular erfolgreich übermittelt: ${ergebnis.id}`);
} catch (fehler) {
console.error('Fehler bei der Formular-Übermittlung:', fehler);
zeigeFehlerMeldung(fehler.message);
}
});async function fehlerhafte() {
try {
// FEHLER: await vergessen
const antwort = fetch('https://api.beispiel.de/daten');
const daten = antwort.json(); // Fehler, da antwort ein Promise ist
return daten;
} catch (fehler) {
// Dieser catch-Block wird den Fehler NICHT abfangen
console.error(fehler);
}
}
// Besser:
async function korrekte() {
try {
const antwort = await fetch('https://api.beispiel.de/daten');
const daten = await antwort.json();
return daten;
} catch (fehler) {
console.error(fehler);
throw fehler;
}
}async function ineffizient() {
// Sequentielle Ausführung, obwohl die Anfragen unabhängig sind
const benutzer = await holeBenutzerdaten();
const produkte = await holeProduktdaten();
return { benutzer, produkte };
}
// Besser (parallel):
async function effizient() {
// Starte beide Anfragen gleichzeitig
const benutzerPromise = holeBenutzerdaten();
const produktePromise = holeProduktdaten();
// Warte auf beide Ergebnisse
const benutzer = await benutzerPromise;
const produkte = await produktePromise;
return { benutzer, produkte };
}
// Oder noch kürzer:
async function effizientKurz() {
const [benutzer, produkte] = await Promise.all([
holeBenutzerdaten(),
holeProduktdaten()
]);
return { benutzer, produkte };
}// PROBLEM: Unbeabsichtigte sequentielle Ausführung in einer Schleife
async function sequentielleVerarbeitung(ids) {
const ergebnisse = [];
for (const id of ids) {
const daten = await holeDaten(id); // Sequentiell: Jeder holeDaten-Aufruf wartet auf den vorherigen
ergebnisse.push(daten);
}
return ergebnisse;
}
// PROBLEM: Falsche Verwendung mit forEach
async function falscheParallelverarbeitung(ids) {
const ergebnisse = [];
// ACHTUNG: forEach wartet NICHT auf asynchrone Callbacks
ids.forEach(async (id) => {
const daten = await holeDaten(id);
ergebnisse.push(daten); // Wird möglicherweise ausgeführt, nachdem die Funktion bereits zurückgekehrt ist
});
return ergebnisse; // Gibt ein leeres Array zurück, bevor die asynchronen Operationen abgeschlossen sind
}
// BESSER: Parallele Ausführung mit Promise.all und map
async function korrekteParallelverarbeitung(ids) {
const ergebnisse = await Promise.all(
ids.map(async (id) => {
return await holeDaten(id);
})
);
return ergebnisse;
}async function unvollstaendigeFehlerbehandlung() {
try {
const antwort = await fetch('https://api.beispiel.de/daten');
const daten = await antwort.json();
return daten;
} catch (fehler) {
console.error('Fehler beim Abrufen der Daten:', fehler);
return []; // Fallback-Wert
}
// Code hier wird nie erreicht, wenn ein Fehler auftritt
await protokolliere('Daten abgerufen'); // Diese Zeile wird übersprungen, wenn ein Fehler auftritt
}
// Besser:
async function vollstaendigeFehlerbehandlung() {
try {
const antwort = await fetch('https://api.beispiel.de/daten');
const daten = await antwort.json();
// Protokollierung innerhalb des try-Blocks
await protokolliere('Daten abgerufen');
return daten;
} catch (fehler) {
console.error('Fehler beim Abrufen der Daten:', fehler);
// Separate Protokollierung für den Fehlerfall
await protokolliere('Fehler beim Datenabruf');
return []; // Fallback-Wert
}
}// In einem modernen Browser
async function ladeFrontendDaten() {
// Fetch API
const antwort = await fetch('/api/daten');
const daten = await antwort.json();
// DOM-Manipulation
document.getElementById('ergebnis').textContent = daten.nachricht;
// Web Storage API
localStorage.setItem('letztesDatum', new Date().toISOString());
// Weitere Browser-APIs
const erlaubnis = await Notification.requestPermission();
if (erlaubnis === 'granted') {
new Notification('Daten aktualisiert');
}
}// In Node.js
const fs = require('fs').promises;
const { promisify } = require('util');
const childProcess = require('child_process');
const exec = promisify(childProcess.exec);
async function backendVerarbeitung() {
// Dateisystem-Operationen
const konfigDaten = await fs.readFile('config.json', 'utf8');
const config = JSON.parse(konfigDaten);
// Externe Prozesse ausführen
const { stdout } = await exec('git status');
console.log('Git Status:', stdout);
// HTTP-Anfragen
const response = await fetch('https://api.extern.de/status');
const apiStatus = await response.json();
// Ergebnisse kombinieren und speichern
const ergebnis = {
config,
gitStatus: stdout,
apiStatus
};
await fs.writeFile('status-report.json', JSON.stringify(ergebnis, null, 2));
return ergebnis;
}