Promises & async/await
JavaScript ist single-threaded — es gibt nur einen Thread, der Code ausführt. Trotzdem kann JavaScript auf Netzwerkantworten warten, Timer setzen oder Dateien lesen, ohne den Rest der Anwendung zu blockieren. Das funktioniert über asynchronen Code.
Das Problem: Blockierender Code
Abschnitt betitelt „Das Problem: Blockierender Code“Stell dir vor, du lädst Daten von einer API. Wenn JavaScript synchron darauf warten würde, wäre die gesamte Seite eingefroren:
// Hypothetisch synchron — so funktioniert es NICHTconst daten = fetchSync("/api/studenten"); // Blockiert alles für 2 Sekundenconsole.log(daten); // Erst danach geht es weiterDeshalb läuft Netzwerk-Kommunikation asynchron: Der Code startet die Anfrage und macht sofort weiter. Das Ergebnis kommt später.
Callbacks — der Anfang
Abschnitt betitelt „Callbacks — der Anfang“Die älteste Lösung sind Callbacks — Funktionen, die aufgerufen werden, wenn die Arbeit fertig ist:
setTimeout(() => { console.log("2 Sekunden vergangen");}, 2000);
console.log("Das kommt zuerst");Ausgabe:
Das kommt zuerst2 Sekunden vergangenDas funktioniert, aber bei mehreren aufeinanderfolgenden asynchronen Operationen wird es schnell unübersichtlich:
// "Callback Hell"getUser(userId, (user) => { getCourses(user.id, (courses) => { getGrades(courses[0].id, (grades) => { console.log(grades); }); });});Promises
Abschnitt betitelt „Promises“Ein Promise ist ein Objekt, das einen zukünftigen Wert repräsentiert. Es kann in drei Zuständen sein:
| Zustand | Bedeutung |
|---|---|
| pending | Die Arbeit läuft noch |
| fulfilled | Erfolgreich abgeschlossen — der Wert ist verfügbar |
| rejected | Fehlgeschlagen — ein Fehler ist aufgetreten |
Promises verwenden
Abschnitt betitelt „Promises verwenden“Die meisten APIs geben dir fertige Promises zurück. fetch ist das bekannteste Beispiel:
const promise = fetch("/api/studenten");console.log(promise); // Promise { <pending> }Um an den Wert zu kommen, verwendest du .then():
fetch("/api/studenten") .then(response => response.json()) .then(daten => { console.log(daten); // Die geladenen Daten });Jedes .then() gibt selbst ein Promise zurück — deshalb kannst du sie verketten:
fetch("/api/studenten") .then(response => response.json()) .then(daten => daten.filter(s => s.aktiv)) .then(aktive => { console.log(aktive); });Fehlerbehandlung mit .catch()
Abschnitt betitelt „Fehlerbehandlung mit .catch()“Wenn ein Promise fehlschlägt oder ein .then()-Callback einen Fehler wirft, springt die Kette zum nächsten .catch():
fetch("/api/studenten") .then(response => { if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); }) .then(daten => { console.log(daten); }) .catch(error => { console.error("Fehler beim Laden:", error.message); });.catch() fängt Fehler aus allen vorherigen .then()-Schritten auf.
.finally()
Abschnitt betitelt „.finally()“Code in .finally() läuft immer — egal ob Erfolg oder Fehler:
fetch("/api/studenten") .then(response => response.json()) .then(daten => console.log(daten)) .catch(error => console.error(error)) .finally(() => { console.log("Anfrage abgeschlossen"); });async/await
Abschnitt betitelt „async/await“async/await ist syntaktischer Zucker über Promises — derselbe Mechanismus, aber mit einer Syntax, die sich wie synchroner Code liest:
async function ladeStudenten() { const response = await fetch("/api/studenten"); const daten = await response.json(); console.log(daten);}await pausiert die Funktion, bis das Promise erfüllt ist, und gibt den Wert zurück. Wichtig: await funktioniert nur innerhalb einer async-Funktion.
Fehlerbehandlung mit try/catch
Abschnitt betitelt „Fehlerbehandlung mit try/catch“Statt .catch() verwendest du try/catch:
async function ladeStudenten() { try { const response = await fetch("/api/studenten");
if (!response.ok) { throw new Error(`HTTP ${response.status}`); }
const daten = await response.json(); console.log(daten); } catch (error) { console.error("Fehler:", error.message); } finally { console.log("Anfrage abgeschlossen"); }}Arrow Functions mit async
Abschnitt betitelt „Arrow Functions mit async“Auch Arrow Functions können async sein:
const ladeStudenten = async () => { const response = await fetch("/api/studenten"); return response.json();};Promises erstellen
Abschnitt betitelt „Promises erstellen“Manchmal musst du selbst ein Promise erstellen — zum Beispiel um einen Timer in ein Promise zu verpacken:
function warte(ms) { return new Promise(resolve => { setTimeout(resolve, ms); });}
async function demo() { console.log("Start"); await warte(2000); console.log("2 Sekunden später");}Der Promise-Konstruktor erhält eine Funktion mit zwei Parametern:
resolve(wert)— das Promise erfolgreich abschließenreject(fehler)— das Promise mit einem Fehler abschließen
function ladeJSON(url) { return new Promise((resolve, reject) => { fetch(url) .then(response => { if (!response.ok) reject(new Error(`HTTP ${response.status}`)); return response.json(); }) .then(resolve) .catch(reject); });}Mehrere Promises parallel
Abschnitt betitelt „Mehrere Promises parallel“Wenn du mehrere unabhängige Anfragen gleichzeitig starten willst:
Promise.all — wartet auf alle Promises. Schlägt fehl, wenn eines fehlschlägt:
async function ladeDashboard(userId) { const [user, kurse, noten] = await Promise.all([ fetch(`/api/users/${userId}`).then(r => r.json()), fetch(`/api/users/${userId}/kurse`).then(r => r.json()), fetch(`/api/users/${userId}/noten`).then(r => r.json()), ]);
console.log(user, kurse, noten);}Alle drei Anfragen laufen gleichzeitig — das ist schneller als nacheinander.
Promise.allSettled — wartet auf alle, auch wenn manche fehlschlagen:
const ergebnisse = await Promise.allSettled([ fetch("/api/studenten"), fetch("/api/kurse"),]);
ergebnisse.forEach(result => { if (result.status === "fulfilled") { console.log("Erfolg:", result.value); } else { console.log("Fehler:", result.reason); }});Verwendung in React
Abschnitt betitelt „Verwendung in React“In React lädst du Daten typischerweise in einem useEffect:
function StudentenListe() { const [studenten, setStudenten] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { async function ladeDaten() { try { const response = await fetch("/api/studenten"); if (!response.ok) throw new Error(`HTTP ${response.status}`); const daten = await response.json(); setStudenten(daten); } catch (err) { setError(err.message); } finally { setLoading(false); } }
ladeDaten(); }, []);
if (loading) return <p>Lädt...</p>; if (error) return <p>Fehler: {error}</p>;
return ( <ul> {studenten.map(s => ( <li key={s.id}>{s.name}</li> ))} </ul> );}