Promises in JavaScript verstehen
Promises in JavaScript verstehen
Promises sind JavaScripts Art, mit asynchronem Code umzugehen. Ein Promise repräsentiert einen Wert, der jetzt, später oder nie verfügbar sein könnte.
Was ist ein Promise?
Ein Promise ist ein Versprechen auf ein zukünftiges Ergebnis:
// Stell dir vor: Du bestellst Pizza
const pizzaVersprechen = bestellePizza();
// Du weißt nicht WANN sie kommt
// Aber du hast ein Versprechen, dass:
// - Sie entweder geliefert wird (fulfilled)
// - Oder ein Problem auftritt (rejected)
Die drei Zustände
┌─────────────┐
│ pending │ ← Warte auf Ergebnis
└─────────────┘
│
├──────────────────┐
│ │
V V
┌─────────────┐ ┌─────────────┐
│ fulfilled │ │ rejected │
│ (success) │ │ (error) │
└─────────────┘ └─────────────┘
Promise erstellen
Mit dem Promise-Konstruktor
const meinPromise = new Promise((resolve, reject) => {
// Asynchrone Operation...
// Bei Erfolg:
resolve("Ergebnis");
// Bei Fehler:
reject(new Error("Etwas ging schief"));
});
Praktisches Beispiel
function warteZufaellig() {
return new Promise((resolve, reject) => {
const wartezeit = Math.random() * 3000;
setTimeout(() => {
if (wartezeit > 2000) {
reject(new Error("Zu lange gewartet!"));
} else {
resolve(`Fertig nach ${Math.round(wartezeit)}ms`);
}
}, wartezeit);
});
}
// Verwenden
warteZufaellig()
.then(ergebnis => console.log(ergebnis))
.catch(fehler => console.error(fehler.message));
Sofort aufgelöste Promises
// Sofort fulfilled
const sofortFertig = Promise.resolve("Direkt verfügbar");
// Sofort rejected
const sofortFehler = Promise.reject(new Error("Sofort fehlgeschlagen"));
// Nützlich für einheitliche APIs
function holeDaten(id) {
if (cache[id]) {
return Promise.resolve(cache[id]); // Aus Cache
}
return fetch(`/api/data/${id}`); // Vom Server
}
then, catch, finally
then - Bei Erfolg
const promise = fetch("/api/users");
promise.then(response => {
console.log("Erfolg!", response);
});
// Kurzform
fetch("/api/users").then(response => console.log(response));
catch - Bei Fehler
fetch("/api/users")
.then(response => response.json())
.catch(error => {
console.error("Fehler:", error.message);
});
finally - Immer ausführen
let isLoading = true;
fetch("/api/users")
.then(response => response.json())
.then(users => {
displayUsers(users);
})
.catch(error => {
showError(error);
})
.finally(() => {
isLoading = false; // Immer ausführen, egal ob Erfolg oder Fehler
hideSpinner();
});
Promise-Ketten
then gibt immer ein neues Promise zurück - perfekt zum Verketten:
fetch("/api/users/1")
.then(response => {
console.log("1. Response erhalten");
return response.json(); // Gibt Promise zurück
})
.then(user => {
console.log("2. User geparst:", user.name);
return fetch(`/api/posts?userId=${user.id}`);
})
.then(response => {
console.log("3. Posts-Response erhalten");
return response.json();
})
.then(posts => {
console.log("4. Posts:", posts.length);
})
.catch(error => {
// Fängt JEDEN Fehler in der Kette!
console.error("Irgendwo ging was schief:", error);
});
Werte in der Kette transformieren
Promise.resolve(5)
.then(x => x * 2) // 10
.then(x => x + 3) // 13
.then(x => x.toString()) // "13"
.then(x => console.log(x)); // Ausgabe: "13"
Return nicht vergessen!
// Falsch - Promise wird nicht weitergegeben
fetch("/api")
.then(response => {
response.json(); // Return fehlt!
})
.then(data => {
console.log(data); // undefined!
});
// Richtig
fetch("/api")
.then(response => {
return response.json();
})
.then(data => {
console.log(data); // Daten!
});
// Oder kurz mit Arrow
fetch("/api")
.then(response => response.json())
.then(data => console.log(data));
Fehlerbehandlung
catch fängt alle Fehler
fetch("/api/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.then(users => {
// Wenn hier ein Fehler passiert...
return processUsers(users);
})
.then(processed => {
// Oder hier...
displayUsers(processed);
})
.catch(error => {
// ...wird er HIER gefangen!
console.error("Fehler in der Kette:", error);
});
Fehler in der Kette behandeln und weitermachen
fetch("/api/users")
.then(response => response.json())
.then(users => {
throw new Error("Test-Fehler");
})
.catch(error => {
console.log("Fehler gefangen:", error.message);
return []; // Fallback-Wert
})
.then(users => {
// Läuft weiter mit leerem Array!
console.log("Users:", users); // []
});
Mehrere catch-Blöcke
fetch("/api")
.then(response => {
if (!response.ok) throw new Error("Netzwerk-Fehler");
return response.json();
})
.catch(error => {
console.log("Netzwerk-Problem:", error);
throw error; // Weitergeben
})
.then(data => processData(data))
.catch(error => {
console.log("Verarbeitungs-Problem:", error);
});
Promise.all - Parallel ausführen
Warte auf alle Promises:
const promise1 = fetch("/api/users").then(r => r.json());
const promise2 = fetch("/api/posts").then(r => r.json());
const promise3 = fetch("/api/comments").then(r => r.json());
Promise.all([promise1, promise2, promise3])
.then(([users, posts, comments]) => {
console.log("Users:", users.length);
console.log("Posts:", posts.length);
console.log("Comments:", comments.length);
})
.catch(error => {
// Wenn EINER fehlschlägt, schlägt alles fehl!
console.error("Mindestens einer fehlgeschlagen:", error);
});
Praktisches Beispiel: Mehrere User laden
const userIds = [1, 2, 3, 4, 5];
const promises = userIds.map(id =>
fetch(`/api/users/${id}`).then(r => r.json())
);
Promise.all(promises)
.then(users => {
console.log("Alle Users geladen:", users);
});
Promise.allSettled - Alle Ergebnisse
Warte auf alle, auch bei Fehlern:
const promises = [
fetch("/api/users").then(r => r.json()),
fetch("/api/invalid").then(r => r.json()), // Wird fehlschlagen
fetch("/api/posts").then(r => r.json())
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Promise ${index}: Erfolg`, result.value);
} else {
console.log(`Promise ${index}: Fehler`, result.reason);
}
});
});
// Nur erfolgreiche extrahieren
Promise.allSettled(promises)
.then(results =>
results
.filter(r => r.status === "fulfilled")
.map(r => r.value)
);
Promise.race - Der Schnellste gewinnt
const schnell = new Promise(resolve =>
setTimeout(() => resolve("Schnell!"), 100)
);
const langsam = new Promise(resolve =>
setTimeout(() => resolve("Langsam..."), 1000)
);
Promise.race([schnell, langsam])
.then(result => console.log(result)); // "Schnell!"
Timeout implementieren
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout!")), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
// Verwendung
fetchWithTimeout("/api/data", 3000)
.then(response => response.json())
.catch(error => console.log(error.message)); // "Timeout!" wenn zu langsam
Promise.any - Erster Erfolg
const promises = [
fetch("https://server1.com/api").then(r => r.json()),
fetch("https://server2.com/api").then(r => r.json()),
fetch("https://server3.com/api").then(r => r.json())
];
Promise.any(promises)
.then(result => {
console.log("Erster erfolgreicher:", result);
})
.catch(error => {
// Nur wenn ALLE fehlschlagen
console.log("Alle fehlgeschlagen");
});
Promises aus Callbacks erstellen
// Alte Callback-API
function alteApi(callback) {
setTimeout(() => callback(null, "Daten"), 1000);
}
// Promise-Wrapper
function neueApi() {
return new Promise((resolve, reject) => {
alteApi((error, daten) => {
if (error) {
reject(error);
} else {
resolve(daten);
}
});
});
}
// Jetzt mit then/catch nutzbar
neueApi().then(daten => console.log(daten));
Utility: promisify
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) reject(error);
else resolve(result);
});
});
};
}
// Verwendung
const readFileAsync = promisify(fs.readFile);
readFileAsync("file.txt", "utf8").then(content => console.log(content));
Häufige Fehler
1. Promise nicht zurückgeben
// Falsch
function getData() {
fetch("/api").then(r => r.json()); // Promise "verschwindet"
}
// Richtig
function getData() {
return fetch("/api").then(r => r.json());
}
2. Verschachtelte Promises
// Falsch - Verschachtelt
fetch("/api/user")
.then(response => {
response.json().then(user => {
fetch(`/api/posts/${user.id}`).then(response => {
// ... Callback Hell mit Promises!
});
});
});
// Richtig - Flache Kette
fetch("/api/user")
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts));
3. Fehler ignorieren
// Falsch - Unhandled Promise Rejection
fetch("/api").then(r => r.json());
// Richtig - Immer catch!
fetch("/api")
.then(r => r.json())
.catch(e => console.error(e));
Zusammenfassung
| Methode | Beschreibung |
|---|---|
new Promise() | Promise erstellen |
.then() | Bei Erfolg |
.catch() | Bei Fehler |
.finally() | Immer ausführen |
Promise.all() | Alle parallel, fail-fast |
Promise.allSettled() | Alle Ergebnisse |
Promise.race() | Schnellster |
Promise.any() | Erster Erfolg |
Promise.resolve() | Sofort fulfilled |
Promise.reject() | Sofort rejected |
Übung: Baue eine retry Funktion mit Promises:
retry(asyncFn, maxAttempts) soll bis zu 3 Mal versuchen, bei Fehler erneut probieren.