Teilfehler behandeln, ohne die gesamte Operation abzubrechen
Promise.allSettled wartet auf alle übergebenen Promises, unabhängig davon, ob einzelne erfüllt oder abgelehnt werden, und liefert für jedes Ergebnis einen eigenen Status. Für Batch-Requests, bei denen ein einzelner Fehler nicht die gesamte Operation zunichtemachen soll, ist das die richtige Grundlage statt des Fail-Fast-Verhaltens von Promise.all.
Inhaltsverzeichnis
- 1. Warum Promise.allSettled ein anderes Fehlermodell braucht
- 2. Syntax und Rückgabeformat im Detail
- 3. Batch-Requests mit Teilfehlern robust verarbeiten
- 4. Ergebnisse nach Status filtern und aggregieren
- 5. Retry-Strategien für fehlgeschlagene Einträge
- 6. Kombination mit Timeout pro Einzel-Promise
- 7. Nutzer-Feedback und Teilerfolge kommunizieren
- 8. Wann sich Promise.allSettled nicht lohnt
- 9. Promise.allSettled vs all vs any vs race
- 10. Zusammenfassung
- 11. FAQ
1. Warum Promise.allSettled ein anderes Fehlermodell braucht
Promise.allSettled wurde eingeführt, weil Promise.all für viele reale Anwendungsfälle das falsche Fehlermodell hat. Promise.all verwirft sofort, sobald irgendeines der übergebenen Promises abgelehnt wird, unabhängig davon, ob die übrigen bereits erfolgreich abgeschlossen sind oder kurz vor dem Abschluss stehen. Bei einem Batch von zwanzig unabhängigen API-Aufrufen bedeutet ein einziger Fehler, dass die Ergebnisse der neunzehn erfolgreichen Aufrufe komplett verloren gehen, weil Promise.all sie nie zurückgibt.
Promise.allSettled löst das, indem es niemals selbst ablehnt. Stattdessen wartet es, bis jedes übergebene Promise entweder erfüllt oder abgelehnt wurde, und liefert für jedes Element ein Ergebnisobjekt mit dem tatsächlichen Status. Das verschiebt die Entscheidung, wie mit einzelnen Fehlern umzugehen ist, vollständig zum Aufrufer, statt sie implizit durch das Fail-Fast-Verhalten von Promise.all vorwegzunehmen. Für Szenarien wie Massen-Imports, parallele Health-Checks oder das Laden mehrerer unabhängiger Widgets auf einer Seite ist das fast immer das passendere Verhalten.
2. Syntax und Rückgabeformat im Detail
Promise.allSettled(promises) nimmt ein iterierbares Objekt von Promises entgegen und gibt selbst ein Promise zurück, das immer erfüllt wird, niemals abgelehnt, sobald alle übergebenen Promises abgeschlossen sind. Das aufgelöste Ergebnis ist ein Array von Objekten, in derselben Reihenfolge wie die Eingabe. Jedes Objekt hat ein Feld status, das entweder 'fulfilled' oder 'rejected' ist.
Bei status: 'fulfilled' enthält das Objekt zusätzlich ein Feld value mit dem aufgelösten Ergebnis. Bei status: 'rejected' enthält es stattdessen ein Feld reason mit dem Fehler oder Ablehnungsgrund. Diese Struktur macht Promise.allSettled zu einem diskriminierten Union-Typ in TypeScript-Terminologie: Anhand des status-Felds lässt sich zuverlässig entscheiden, ob value oder reason gelesen werden soll, ohne try/catch pro Element.
const results = await Promise.allSettled([
fetch('/api/users').then((r) => r.json()),
fetch('/api/orders').then((r) => r.json()),
fetch('/api/broken-endpoint').then((r) => r.json()),
]);
console.log(results);
// [
// { status: 'fulfilled', value: [...users] },
// { status: 'fulfilled', value: [...orders] },
// { status: 'rejected', reason: TypeError: Failed to fetch }
// ]
3. Batch-Requests mit Teilfehlern robust verarbeiten
Der klassische Anwendungsfall für Promise.allSettled ist ein Massen-Import oder Massen-Export, bei dem jeder einzelne Datensatz unabhängig von den übrigen verarbeitet wird. Ein Import von tausend Produkten in ein Shop-System etwa soll nicht komplett abbrechen, nur weil ein einzelner Datensatz ein ungültiges Feld enthält. Mit Promise.allSettled laufen alle tausend Verarbeitungsversuche parallel, und am Ende steht fest, welche erfolgreich waren und welche mit welchem Grund fehlgeschlagen sind.
Diese Vorgehensweise verändert auch das Monitoring solcher Batch-Operationen grundlegend. Statt eines binären Erfolg-oder-Misserfolg-Zustands liefert Promise.allSettled eine granulare Erfolgsquote, die sich direkt in ein Dashboard überführen lässt: 987 von 1000 erfolgreich, 13 fehlgeschlagen mit jeweils spezifischer Fehlermeldung. Diese Granularität ist mit Promise.all praktisch unmöglich zu erreichen, weil dort der erste Fehler bereits alle weiteren Informationen verwirft.
async function importProducts(products) {
const results = await Promise.allSettled(
products.map((product) => importSingleProduct(product))
);
const succeeded = results.filter((r) => r.status === 'fulfilled').length;
const failed = results.filter((r) => r.status === 'rejected').length;
console.log(`Import finished: ${succeeded} succeeded, ${failed} failed`);
return results;
}
4. Ergebnisse nach Status filtern und aggregieren
Nach dem Auflösen von Promise.allSettled ist die häufigste Folgeoperation das Trennen von erfolgreichen und fehlgeschlagenen Ergebnissen in zwei separate Listen. Das geschieht typischerweise mit zwei filter-Aufrufen auf dem status-Feld, gefolgt von einem map, das entweder value oder reason extrahiert. Diese Trennung ist der Punkt, an dem die eigentliche Geschäftslogik entscheidet, was mit erfolgreichen und was mit fehlgeschlagenen Einträgen geschieht.
Ein häufiger Fehler bei der Nutzung von Promise.allSettled ist, das Filtern nach status zu vergessen und stattdessen direkt auf value zuzugreifen, was bei einem abgelehnten Eintrag undefined liefert, statt eines Fehlers. TypeScript hilft hier direkt, weil der diskriminierte Union-Typ ohne vorherigen status-Check den Zugriff auf value gar nicht zulässt. In reinem JavaScript lohnt sich eine kleine Utility-Funktion, die diese Unterscheidung kapselt.
function partitionSettled(results) {
const fulfilled = [];
const rejected = [];
for (const result of results) {
if (result.status === 'fulfilled') {
fulfilled.push(result.value);
} else {
rejected.push(result.reason);
}
}
return { fulfilled, rejected };
}
const { fulfilled, rejected } = partitionSettled(results);
console.log(`${fulfilled.length} values, ${rejected.length} errors`);
5. Retry-Strategien für fehlgeschlagene Einträge
Weil Promise.allSettled jeden fehlgeschlagenen Eintrag inklusive Originalindex zurückliefert, lässt sich darauf direkt eine Retry-Strategie aufbauen. Statt den gesamten Batch erneut auszuführen, wenn ein Teil fehlgeschlagen ist, wird nur die Teilmenge der fehlgeschlagenen Elemente erneut mit Promise.allSettled verarbeitet. Das reduziert unnötige Last auf externen Systemen erheblich, besonders wenn der ursprüngliche Batch mehrere tausend Elemente umfasste und nur eine kleine Minderheit tatsächlich gescheitert ist.
Eine robuste Retry-Implementierung begrenzt die Anzahl der Versuche und wartet zwischen den Versuchen mit exponentiellem Backoff, um transiente Fehler wie kurzzeitige Netzwerkprobleme oder Rate-Limiting abzufedern. Da Promise.allSettled selbst niemals ablehnt, lässt sich diese Schleife ohne zusätzliches try/catch um den gesamten Batch schreiben, jeder einzelne Retry-Durchlauf liefert wieder ein vollständiges Ergebnis-Array.
async function importWithRetry(items, maxAttempts = 3) {
let pending = items.map((item, index) => ({ item, index }));
const finalResults = new Array(items.length);
for (let attempt = 1; attempt <= maxAttempts && pending.length > 0; attempt++) {
const results = await Promise.allSettled(
pending.map(({ item }) => importSingleProduct(item))
);
const stillFailing = [];
results.forEach((result, i) => {
const { index } = pending[i];
if (result.status === 'fulfilled') {
finalResults[index] = result;
} else {
stillFailing.push(pending[i]);
}
});
pending = stillFailing;
if (pending.length > 0) {
await new Promise((r) => setTimeout(r, 500 * attempt)); // backoff
}
}
return finalResults;
}
6. Kombination mit Timeout pro Einzel-Promise
Ein wichtiges Detail bei Promise.allSettled: Ein hängender Request, der nie auflöst, blockiert das gesamte Ergebnis, weil Promise.allSettled auf jeden einzelnen Eintrag wartet. Ohne ein Timeout pro Promise kann ein einziger langsamer oder hängender Request die gesamte Batch-Operation unbegrenzt verzögern, selbst wenn alle anderen Requests längst abgeschlossen sind. Deshalb ist es üblich, jedes einzelne Promise vor der Übergabe an Promise.allSettled mit einem eigenen Timeout zu versehen.
Kombiniert mit AbortSignal.timeout() lässt sich das direkt in der Erzeugung jedes Einzel-Promises einbauen, sodass ein hängender Request nach einer definierten Frist automatisch als abgelehnt in das Gesamtergebnis von Promise.allSettled einfließt, statt den gesamten Batch aufzuhalten. Diese Kombination ist in der Praxis fast immer sinnvoll, sobald ein Batch aus Netzwerk-Requests besteht, deren Laufzeit nicht garantiert ist.
async function fetchWithTimeout(url, timeoutMs = 5000) {
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
return response.json();
}
const results = await Promise.allSettled(
endpoints.map((url) => fetchWithTimeout(url, 3000))
);
// A hanging endpoint rejects with TimeoutError after 3s instead of blocking forever
7. Nutzer-Feedback und Teilerfolge kommunizieren
In UI-Kontexten erlaubt Promise.allSettled eine deutlich bessere Nutzerkommunikation als ein binäres Erfolg-Fehler-Modell. Ein Formular, das mehrere Dateien gleichzeitig hochlädt, kann mit Promise.allSettled genau anzeigen, welche Dateien erfolgreich waren und welche mit welchem Grund fehlgeschlagen sind, statt bei der ersten fehlgeschlagenen Datei den gesamten Upload-Vorgang als gescheitert zu melden und dem Nutzer keine Information über die übrigen Dateien zu geben.
Diese granulare Rückmeldung reduziert Support-Anfragen spürbar, weil Nutzer direkt sehen, welcher Teil ihrer Aktion erfolgreich war und welcher wiederholt werden muss. Statt „Upload fehlgeschlagen“ zeigt die Oberfläche „acht von zehn Dateien hochgeladen, zwei Dateien überschreiten die Größenbeschränkung“ an, was die Fehlerursache direkt benennt und dem Nutzer eine konkrete Handlungsoption gibt.
8. Wann sich Promise.allSettled nicht lohnt
Nicht jeder Anwendungsfall profitiert von Promise.allSettled. Wenn alle Operationen voneinander abhängen und ein Fehler in einer Operation die übrigen Ergebnisse ohnehin wertlos macht, etwa beim parallelen Laden mehrerer Teile einer einzigen zusammenhängenden Datenstruktur, ist das Fail-Fast-Verhalten von Promise.all tatsächlich die richtige Wahl. Promise.allSettled würde in diesem Fall unnötig lange auf Ergebnisse warten, die am Ende ohnehin verworfen werden müssen.
Ein zweiter Punkt betrifft die Fehlerbehandlung selbst: Promise.allSettled verschiebt die Verantwortung, jeden reason zu inspizieren, auf den Aufrufer. Wer diese Verantwortung nicht wahrnimmt und Fehler stillschweigend ignoriert, verliert genau die Informationen, die Promise.all durch sein Ablehnen automatisch sichtbar gemacht hätte. Promise.allSettled ist ein Werkzeug für explizite, durchdachte Fehlerbehandlung, nicht für das Verstecken von Fehlern.
9. Promise.allSettled vs all vs any vs race
Die vier statischen Promise-Kombinatoren unterscheiden sich grundlegend in ihrem Fehlerverhalten und in dem, was sie über den Zustand aller beteiligten Promises preisgeben. Die folgende Tabelle stellt sie gegenüber.
| Methode | Löst auf bei | Lehnt ab bei | Typischer Einsatz |
|---|---|---|---|
Promise.allSettled |
Alle abgeschlossen (egal wie) | Nie | Batch-Requests mit erlaubten Teilfehlern |
Promise.all |
Alle erfüllt | Erster Fehler | Voneinander abhängige Operationen |
Promise.any |
Erster Erfolg | Alle fehlgeschlagen | Erste erfolgreiche Quelle von mehreren |
Promise.race |
Erstes Settlement (Erfolg oder Fehler) | Erstes Settlement ist Fehler | Timeout-Wettlauf gegen eine Operation |
Die Auswahl der richtigen Methode hängt direkt davon ab, ob Teilfehler akzeptabel sind (allSettled), ob alle Ergebnisse zwingend benötigt werden (all), ob nur ein einziger Erfolg genügt (any), oder ob nur das schnellste Ergebnis zählt (race).
Mironsoft
Robuste Batch-Verarbeitung und Fehlerbehandlung in JavaScript
Ein Fehler bringt euren gesamten Batch zum Absturz?
Wir bauen Batch-Verarbeitung und Massen-Imports mit Promise.allSettled, granularer Fehlerauswertung und Retry-Strategien für fehlgeschlagene Einträge.
Audit
Fail-Fast-Stellen identifizieren, die Teilfehler eigentlich vertragen
Umbau
Migration auf Promise.allSettled mit Retry und Timeout-Handling
Monitoring
Granulare Erfolgsquoten für Batch-Operationen im Dashboard
10. Zusammenfassung
Promise.allSettled ist die richtige Wahl, sobald einzelne Fehler in einem Batch von Operationen erwartbar und akzeptabel sind. Anders als Promise.all lehnt es niemals selbst ab, sondern liefert für jedes übergebene Promise ein Ergebnisobjekt mit status, value oder reason, wodurch erfolgreiche und fehlgeschlagene Einträge sauber getrennt und unabhängig weiterverarbeitet werden können.
In der Praxis lohnt sich die Kombination mit Timeout pro Einzel-Promise, einer Retry-Strategie für fehlgeschlagene Einträge und granularem Nutzer-Feedback über Teilerfolge. Wo alle Operationen zwingend voneinander abhängen, bleibt das Fail-Fast-Verhalten von Promise.all die passendere Wahl, aber für unabhängige Batch-Operationen ist Promise.allSettled fast immer die robustere Grundlage.
Promise.allSettled praktisch nutzen — Das Wichtigste auf einen Blick
Rückgabeformat
Array von Objekten mit status: 'fulfilled' oder 'rejected', jeweils value oder reason.
Kein Fail-Fast
Promise.allSettled lehnt selbst niemals ab, wartet immer auf alle übergebenen Promises.
Retry gezielt
Nur fehlgeschlagene Einträge erneut verarbeiten, statt den gesamten Batch zu wiederholen.
Timeout pro Promise
AbortSignal.timeout() pro Einzel-Promise verhindert, dass ein hängender Request den gesamten Batch blockiert.