Zum Hauptinhalt springen

Promise-Kombinatoren

· 4 Minuten Lesezeit
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Seit der Einführung von Promises in ES2015 unterstützt JavaScript genau zwei Promise-Kombinatoren: die statischen Methoden Promise.all und Promise.race.

Zwei neue Vorschläge befinden sich derzeit im Standardisierungsprozess: Promise.allSettled und Promise.any. Mit diesen Ergänzungen gibt es insgesamt vier Promise-Kombinatoren in JavaScript, die jeweils unterschiedliche Anwendungsfälle ermöglichen.

Hier ist ein Überblick über die vier Kombinatoren:

NameBeschreibungStatus
Promise.allSettledbricht nicht abhinzugefügt in ES2020 ✅
Promise.allbricht ab, wenn ein Eingabewert abgelehnt wirdhinzugefügt in ES2015 ✅
Promise.racebricht ab, wenn ein Eingabewert abgeschlossen isthinzugefügt in ES2015 ✅
Promise.anybricht ab, wenn ein Eingabewert erfüllt wirdhinzugefügt in ES2021 ✅

Schauen wir uns ein Beispiel für jeden Kombinator an.

Promise.all

Promise.all informiert Sie, wenn entweder alle Eingabepromises erfüllt wurden oder eines davon abgelehnt wird.

Stellen Sie sich vor, der Benutzer klickt auf eine Schaltfläche und Sie möchten einige Stylesheets laden, um eine komplett neue Benutzeroberfläche darzustellen. Dieses Programm startet für jedes Stylesheet eine HTTP-Anfrage parallel:

const promises = [
fetch('/component-a.css'),
fetch('/component-b.css'),
fetch('/component-c.css'),
];
try {
const styleResponses = await Promise.all(promises);
enableStyles(styleResponses);
renderNewUi();
} catch (reason) {
displayError(reason);
}

Sie möchten die neue Benutzeroberfläche nur dann rendern, wenn alle Anfragen erfolgreich waren. Wenn etwas schiefgeht, möchten Sie stattdessen so schnell wie möglich eine Fehlermeldung anzeigen, ohne auf andere Prozesse zu warten.

In einem solchen Fall könnten Sie Promise.all verwenden: Sie möchten wissen, wann alle Promises erfüllt sind, oder sobald eines von ihnen abgelehnt wird.

Promise.race

Promise.race ist nützlich, wenn Sie mehrere Promises ausführen möchten und entweder…

  1. etwas mit dem ersten erfolgreichen Ergebnis machen, das eingeht (falls eines der Promises erfüllt wird), oder
  2. etwas tun, sobald eines der Promises abgelehnt wird.

Das heißt, wenn eines der Promises abgelehnt wird, möchten Sie diese Ablehnung beibehalten, um den Fehlerfall separat zu behandeln. Das folgende Beispiel macht genau das:

try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
renderResult(result);
} catch (error) {
renderError(error);
}

Wir starten eine rechnerisch aufwendige Aufgabe, die lange dauern könnte, aber wir lassen sie mit einem Promise konkurrieren, das nach 2 Sekunden abgelehnt wird. Abhängig von dem ersten Promise, das erfüllt oder abgelehnt wird, rendern wir entweder das berechnete Ergebnis oder die Fehlermeldung in zwei separaten Codepfaden.

Promise.allSettled

Promise.allSettled gibt Ihnen ein Signal, wenn alle Eingabepromises abgewickelt sind, das heißt, sie sind entweder erfüllt oder abgelehnt. Dies ist nützlich in Fällen, in denen es Ihnen nicht um den Zustand des Promises geht, sondern Sie einfach nur wissen möchten, wann die Arbeit erledigt ist, unabhängig davon, ob sie erfolgreich war.

Zum Beispiel können Sie eine Reihe unabhängiger API-Aufrufe starten und Promise.allSettled verwenden, um sicherzustellen, dass sie alle abgeschlossen sind, bevor Sie etwas anderes tun, wie z. B. das Entfernen eines Ladeindikators:

const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Stellen Sie sich vor, einige dieser Anfragen schlagen fehl, und einige sind erfolgreich.

await Promise.allSettled(promises);
// Alle API-Aufrufe wurden abgeschlossen (entweder fehlgeschlagen oder erfolgreich).
removeLoadingIndicator();

Promise.any

Promise.any gibt Ihnen ein Signal, sobald eines der Versprechen erfüllt ist. Dies ist ähnlich wie Promise.race, außer dass any nicht frühzeitig abbricht, wenn eines der Versprechen abgelehnt wird.

const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// Eines der Versprechen wurde erfüllt.
console.log(first);
// → z. B. 'b'
} catch (error) {
// Alle Versprechen wurden abgelehnt.
console.assert(error instanceof AggregateError);
// Protokollieren Sie die Ablehnungswerte:
console.log(error.errors);
// → [
// <TypeError: Failed to fetch /endpoint-a>,
// <TypeError: Failed to fetch /endpoint-b>,
// <TypeError: Failed to fetch /endpoint-c>
// ]
}

Dieses Codebeispiel überprüft, welcher Endpunkt am schnellsten antwortet, und protokolliert ihn dann. Nur wenn alle Anfragen fehlschlagen, gelangen wir in den catch-Block, wo wir dann die Fehler behandeln können.

Promise.any-Ablehnungen können mehrere Fehler gleichzeitig darstellen. Um dies auf Sprachebene zu unterstützen, wurde ein neuer Fehlertyp namens AggregateError eingeführt. Zusätzlich zur grundlegenden Verwendung im obigen Beispiel können AggregateError-Objekte auch programmgesteuert erstellt werden, genau wie die anderen Fehlertypen:

const aggregateError = new AggregateError([errorA, errorB, errorC], 'Es ist etwas schief gelaufen!');