Promise.allSettled praktisch nutzen in JavaScript
AI generated
JS
() =>
JavaScript · Async · Fehlerbehandlung · Batch-Verarbeitung
Promise.allSettled praktisch nutzen
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.

15 Min. Lesezeit Promise.allSettled · status · value · reason ES2020+ · Node · Browser

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.

11. FAQ: Promise.allSettled praktisch nutzen

1Unterschied zu Promise.all?
Promise.allSettled lehnt nie ab und wartet auf alle Promises, Promise.all lehnt beim ersten Fehler sofort ab.
2Wie sieht das Rückgabeformat aus?
Ein Array mit status-Feld pro Element, dazu value bei fulfilled oder reason bei rejected.
3Wann nutzen statt Promise.all?
Wenn Teilfehler in unabhängigen Operationen akzeptabel sind und die erfolgreichen Ergebnisse trotzdem gebraucht werden.
4Wie filtere ich Ergebnisse?
Mit filter auf status, gefolgt von map zur Extraktion von value oder reason.
5Retry-Strategie möglich?
Ja, fehlgeschlagene Einträge samt Index isolieren und gezielt in einem erneuten Durchlauf wiederholen.
6Blockiert ein hängender Request alles?
Ja ohne eigenes Timeout, deshalb AbortSignal.timeout() pro Einzel-Promise einsetzen.
7Browser-Support?
Seit ES2020 vollständig in Node.js und allen modernen Browsern, kein Polyfill nötig.
8Unterschied zu Promise.any?
Promise.any löst beim ersten Erfolg auf und lehnt nur ab, wenn alles fehlschlägt. AllSettled wartet immer auf alle.
9Teilerfolge im UI kommunizieren?
Mit einer Zusammenfassung aus Erfolgs- und Fehlerzahl statt einer binären Meldung.
10Wann ist Promise.all trotzdem besser?
Wenn alle Operationen voneinander abhängen und ein Fehler alle übrigen Ergebnisse wertlos macht.