Zum Inhalt springen
aviral gupta

// I1.4 · ca. 33 Min. · Aufbau

Promise.all, allSettled, any und race

Nach dieser Lektion wählen Sie für eine Gruppe von Promises den passenden Kombinator: jedes Ergebnis, jeden Ausgang, den ersten Erfolg oder den ersten, der fertig wird, womit Sie auch ein Timeout bauen.

Lektion 4 von 6 in I1 Asynchrones JavaScript

Danach können Sie

  • Promise.all und Promise.allSettled benutzen und sagen, was jedes liefert, wenn eine Eingabe fehlschlägt
  • Promise.any für den ersten Erfolg benutzen und den AggregateError lesen, wenn alle Eingaben fehlschlagen
  • Promise.race für den Ersten benutzen, der fertig wird, und damit ein Timeout bauen
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion I1.1. b schlägt fehl. Was gibt dieser Code aus? (fake wird nach seinen ms erledigt.)

    try {
      console.log(await Promise.all([fake("a", 20), fake("b", 10, false)]));
    } catch (error) {
      console.log("caught:", error.message);
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Der schnellste Spiegelserver fällt aus. Was liefert Promise.any?

    const file = await Promise.any([fake("mirror 1", 30), fake("mirror 2", 10, false), fake("mirror 3", 20)]);
    console.log(file);
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie den Kombinator, der jeden Ausgang meldet, sodass „fulfilled a, rejected b failed“ ausgegeben wird.

    const results = await Promise.____([fake("a", 20), fake("b", 10, false)]);
    console.log(results.map((r) => r.status + " " + (r.value ?? r.reason.message)).join(", "));
    await Promise.([fake("a", 20), fake("b", 10, false)]);
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Kombinator zu, wann sein Promise erledigt wird.

  5. Üben · Aufgabe 5 von 7

    Ein Wettlauf gegen ein Timeout von 20 ms. Was gibt dieser Code aus?

    const timeout = (ms) => new Promise((resolve, reject) => setTimeout(() => reject(new Error("timed out")), ms));
    
    try {
      console.log(await Promise.race([fake("report", 50), timeout(20)]));
    } catch (error) {
      console.log("error:", error.message);
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Jede Eingabe schlägt fehl. Was gibt dieser Code aus?

    try {
      await Promise.any([
        Promise.reject(new Error("first")),
        Promise.reject(new Error("second"))
      ]);
    } catch (error) {
      console.log(error.name, error.errors.length, error.errors[1].message);
    }
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe: Wetter aus drei Quellen. Schreiben Sie source(name, ms, ok), das nach ms mit "name: 21 °C" erfüllt oder mit "name down" abgelehnt wird. Benutzen Sie dann jeden Kombinator einmal: all, wenn jede Quelle antworten muss, allSettled für einen Bericht über jeden Ausgang, any für die schnellste funktionierende Quelle, während die schnellste ausfällt, und race gegen ein Timeout von 15 ms. Geben Sie jedes Ergebnis aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Vier Kombinatoren im Vergleich

fake() wird nach einer Verzögerung erledigt, erfüllt mit seinem Namen oder abgelehnt. Jeder Kombinator bekommt eine Gruppe davon. all liefert jeden Wert in der Reihenfolge der Eingabe oder scheitert an b. allSettled meldet beide Ausgänge. any findet den ersten Spiegelserver, der funktioniert, und wirft einen AggregateError, wenn alle ausfallen. race nimmt den Ersten, der erledigt ist, und mit einem Timer, der ablehnt, ist das ein Timeout.

main.js

// fake(name, ms, ok) settles after ms milliseconds: fulfilled with name, or rejected.
const fake = (name, ms, ok = true) =>
  new Promise((resolve, reject) =>
    setTimeout(() => (ok ? resolve(name) : reject(new Error(`${name} failed`))), ms)
  );

console.log("all:", await Promise.all([fake("a", 30), fake("b", 10)]));
try {
  await Promise.all([fake("a", 30), fake("b", 10, false)]);
} catch (error) {
  console.log("all failed:", error.message);
}

const settled = await Promise.allSettled([fake("a", 30), fake("b", 10, false)]);
console.log("allSettled:", settled.map((r) => r.status));

console.log("any:", await Promise.any([fake("mirror 1", 30), fake("mirror 2", 10, false), fake("mirror 3", 20)]));
try {
  await Promise.any([fake("x", 10, false), fake("y", 20, false)]);
} catch (error) {
  console.log("any failed:", error.name, error.errors.map((e) => e.message));
}

const timeout = (ms) =>
  new Promise((resolve, reject) => setTimeout(() => reject(new Error(`timed out after ${ms} ms`)), ms));
console.log("race:", await Promise.race([fake("quick", 10), timeout(50)]));
try {
  await Promise.race([fake("slow", 80), timeout(20)]);
} catch (error) {
  console.log("race failed:", error.message);
}

Ausführen mit

node main.js

Ausgabe

all: [ 'a', 'b' ]
all failed: b failed
allSettled: [ 'fulfilled', 'rejected' ]
any: mirror 3
any failed: AggregateError [ 'x failed', 'y failed' ]
race: quick
race failed: timed out after 20 ms
  • all behält die Reihenfolge der Eingabe: a zuerst, obwohl b zuerst fertig war.
  • any übersprang mirror 2, der zuerst ausfiel, und nahm mirror 3.
  • Das Array errors des AggregateError behält die Reihenfolge der Eingabe: x, dann y.
  • Das langsame Promise im letzten race lief bis zum Ende; race hörte nur auf, darauf zu warten.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Ein Timeout, das ablehnt

withTimeout(promise, ms) soll so erledigt werden wie promise, wenn es innerhalb von ms Millisekunden erledigt ist, und sonst mit einem Error abgelehnt werden, dessen Meldung timed out after <ms> ms lautet. Der Startcode lässt promise gegen einen Timer antreten, der erfüllt, ein langsames Promise ergibt also undefined statt eines Fehlers. Lassen Sie den Timer ablehnen.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Der langsame Fall ergibt undefined. Womit wird das Timer-Promise erledigt?

  2. Hinweis 2

    race übernimmt die erste Eingabe, die erledigt wird, erfüllt oder abgelehnt. Der Timer muss ablehnen, damit race ablehnt.

  3. Hinweis 3

    new Promise((resolve, reject) => setTimeout(() => reject(new Error(`timed out after ${ms} ms`)), ms))

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export function withTimeout(promise, ms) {
  const timer = new Promise((resolve, reject) =>
    setTimeout(() => reject(new Error(`timed out after ${ms} ms`)), ms)
  );
  return Promise.race([promise, timer]);
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export function withTimeout(promise, ms) {
  const timer = new Promise((resolve) => setTimeout(resolve, ms));
  return Promise.race([promise, timer]);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {withTimeout} from './main.js';

const later = (value, ms) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

test('Ein schnelles Promise liefert seinen Wert', async () => {
  const got = await withTimeout(later('report', 5), 50);
  assert.equal(got, 'report', `withTimeout gab ${got}`);
});

test('Ein langsames Promise wird mit timed out after 20 ms abgelehnt', async () => {
  await assert.rejects(withTimeout(later('report', 60), 20), {message: 'timed out after 20 ms'}, 'withTimeout sollte ablehnen, wenn das Promise zu langsam ist');
});

test('Eine schnelle Ablehnung wird unverändert durchgereicht', async () => {
  await assert.rejects(withTimeout(Promise.reject(new Error('offline')), 50), {message: 'offline'}, 'withTimeout sollte mit dem eigenen Fehler des Promise ablehnen');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Der erste Spiegelserver, der funktioniert

firstAvailable(mirrors) bekommt ein Array von Funktionen; jede startet einen Download und gibt ein Promise zurück. Es soll alle starten und das erste erfolgreiche Ergebnis zurückgeben, auch wenn ein schnellerer Spiegelserver ausfällt. Fallen alle aus, wird es mit einem Error abgelehnt, dessen Meldung no mirror available lautet. Der Startcode benutzt race, ein schneller Fehlschlag gewinnt also. Benutzen Sie den richtigen Kombinator.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Der ausgefallene Spiegelserver us gewinnt das Rennen. Welcher Kombinator ignoriert Ablehnungen?

  2. Hinweis 2

    Promise.any wird mit dem ersten Erfolg erfüllt und nur abgelehnt, wenn jede Eingabe abgelehnt wurde.

  3. Hinweis 3

    Packen Sie await Promise.any(…) in try/catch und werfen Sie im catch throw new Error("no mirror available").

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export async function firstAvailable(mirrors) {
  try {
    return await Promise.any(mirrors.map((start) => start()));
  } catch {
    throw new Error("no mirror available");
  }
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export async function firstAvailable(mirrors) {
  return Promise.race(mirrors.map((start) => start()));
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {firstAvailable} from './main.js';

const mirror = (name, ms, ok = true) => () =>
  new Promise((resolve, reject) => setTimeout(() => (ok ? resolve(name) : reject(new Error(name + ' down'))), ms));

test('Der schnellste funktionierende Spiegelserver gewinnt', async () => {
  const got = await firstAvailable([mirror('eu', 30), mirror('us', 10)]);
  assert.equal(got, 'us', `firstAvailable gab ${got}`);
});

test('Ein schnellerer Spiegelserver, der ausfällt, wird übersprungen', async () => {
  const got = await firstAvailable([mirror('eu', 30), mirror('us', 5, false)]);
  assert.equal(got, 'eu', `firstAvailable gab ${got}`);
});

test('Fallen alle aus, wird mit no mirror available abgelehnt', async () => {
  await assert.rejects(firstAvailable([mirror('eu', 10, false), mirror('us', 5, false)]), {message: 'no mirror available'}, 'firstAvailable sollte mit no mirror available ablehnen');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Promises einzeln übergeben statt in einem Array

const a = Promise.resolve("a");
const b = Promise.resolve("b");
const values = await Promise.all(a, b);
console.log(values);

Was Node.js ausgibt

TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

Warum, und die Lösung

Jeder Kombinator nimmt als einziges Argument ein iterierbares Objekt, meist ein Array. Promise.all(a, b) behandelt das Promise a als dieses iterierbare Objekt und ignoriert b, und ein Promise lässt sich nicht durchlaufen. Setzen Sie die Eingaben in eckige Klammern: await Promise.all([a, b]).

Die Funktionen übergeben, statt sie aufzurufen

const loadA = async () => "a";
const loadB = async () => "b";
const [first] = await Promise.all([loadA, loadB]);
console.log(first.toUpperCase());

Was Node.js ausgibt

TypeError: first.toUpperCase is not a function

Warum, und die Lösung

Ein Wert im Array, der kein Promise ist, wird unverändert durchgereicht. Hier sind diese Werte die Funktionen selbst, first ist also die Funktion loadA, und es wurde gar nichts geladen. Rufen Sie jede Funktion auf, damit das Array laufende Promises enthält: Promise.all([loadA(), loadB()]).

Promise.any ohne catch, wenn alles fehlschlägt

const down = (name) => Promise.reject(new Error(`${name} down`));
const file = await Promise.any([down("eu"), down("us")]);
console.log(file);

Was Node.js ausgibt

AggregateError: All promises were rejected

Warum, und die Lösung

Wenn jede Eingabe abgelehnt wird, lehnt any mit einem AggregateError ab, und nichts fängt ihn hier ab, das Programm stoppt also. Die Meldung sagt nur, dass alle abgelehnt wurden; die Gründe stehen in error.errors. Warten Sie in try/catch darauf und lesen Sie error.errors, um den Grund jedes Spiegelservers zu sehen.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

all und allSettled: die ganze Gruppe

Promise.all([p1, p2]) wird mit einem Array von Werten in der Reihenfolge der Eingabe erfüllt, egal in welcher Reihenfolge sie fertig wurden. Es wird abgelehnt, sobald eine Eingabe abgelehnt wird, mit genau diesem ersten Grund, und die übrigen Ergebnisse gehen verloren. Promise.allSettled wird nie abgelehnt: Es wartet auf jede Eingabe und liefert pro Eingabe ein Objekt, {status: "fulfilled", value} oder {status: "rejected", reason}. Bei einem leeren Array werden beide sofort mit [] erfüllt. Nehmen Sie all, wenn Sie jedes Ergebnis brauchen, und allSettled, wenn Sie jeden Ausgang melden müssen.

any: der erste Erfolg

Promise.any wird mit dem Wert der ersten Eingabe erfüllt, die erfüllt wird, und ignoriert bis dahin Ablehnungen, ein schneller Fehlschlag hält es also nicht auf. Nur wenn jede Eingabe abgelehnt wird, lehnt es selbst ab, mit einem AggregateError: Seine Meldung lautet in V8 All promises were rejected, und seine Eigenschaft errors enthält jeden Grund, in der Reihenfolge der Eingabe. Ein leeres Array wird sofort abgelehnt, weil nichts gelingen kann. Nehmen Sie any für redundante Quellen, etwa mehrere Spiegelserver für dieselbe Datei.

race: der Erste, der fertig wird

Promise.race wird so erledigt wie die Eingabe, die zuerst erledigt ist, erfüllt oder abgelehnt. Der klassische Einsatz ist ein Timeout: Lassen Sie die Arbeit gegen ein Promise antreten, das nach ms Millisekunden abgelehnt wird. Wer zuerst kommt, gewinnt. Keiner der vier Kombinatoren bricht etwas ab: Der unterlegene Timer feuert trotzdem, und die langsame Arbeit läuft bis zum Ende; echten Abbruch bietet AbortController, in Lektion I6.3. Ein leeres Array lässt race für immer offen. Jeder Kombinator erwartet ein einziges iterierbares Objekt, etwa ein Array, mit Promises, die schon laufen.

Quellen

Zuletzt geprüft am 5. Oktober 2026