</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #2

JavaScript Promises: ein praktischer Leitfaden

Verstehen Sie JavaScript-Promises — sie erstellen, then/catch/finally, Verkettung und das Kombinieren vieler mit Promise.all, race, any und allSettled — mit ausführbaren Beispielen und den häufigsten Fallstricken.

Ein rotes Puzzleteil, das in die letzte Lücke eines grauen Puzzles einrastet

Ein Promise ist ein Objekt, das einen Wert darstellt, der noch nicht bereit ist. Es ist die Grundlage des asynchronen JavaScript: fetch(), Timer, Dateilesevorgänge in Node und die meisten modernen APIs geben Promises zurück. Ein Promise befindet sich immer in einem von drei Zuständen — ausstehend, erfüllt oder abgelehnt — und sobald es entschieden ist, ändert es sich nie wieder.

Ein Promise erstellen

Meistens verwenden Sie Promises, die Ihnen eine API übergibt. Aber Sie erstellen selbst eines mit dem Promise-Konstruktor, der eine Executor-Funktion entgegennimmt, die resolve und reject erhält:

const wait = (ms) => new Promise((resolve) => {
  setTimeout(resolve, ms);
});

const fetchUser = (id) => new Promise((resolve, reject) => {
  if (!id) reject(new Error('id is required'));
  else resolve({ id, name: 'Ada' });
});

Der Aufruf von resolve(value) erfüllt das Promise mit diesem Wert; der Aufruf von reject(error) lehnt es ab. Alles, was synchron innerhalb des Executors geworfen wird, lehnt das Promise ebenfalls ab.

then, catch und finally

Sie lesen das Ergebnis eines Promise mit .then() für Erfolg, .catch() für Fehlschlag und .finally() für Aufräumarbeiten, die in beiden Fällen laufen:

fetchUser(42)
  .then((user) => console.log('Got', user.name))
  .catch((err) => console.error('Failed:', err.message))
  .finally(() => console.log('Done'));

.then() kann zwei Argumente annehmen — onFulfilled und onRejected — aber ein eigenes .catch() am Ende der Kette ist klarer und fängt zudem Fehler ab, die in früheren .then()-Handlern geworfen wurden.

Verkettung: die eigentliche Superkraft

Jedes .then() gibt ein neues Promise zurück, daher lassen sie sich verketten. Was immer Sie aus einem Handler zurückgeben, wird zur Eingabe des nächsten. Entscheidend ist: Wenn Sie ein Promise zurückgeben, wartet die Kette, bis es entschieden ist, bevor sie fortfährt:

fetch('/api/user')
  .then((res) => res.json())          // returns a promise → chain waits
  .then((user) => fetch('/api/posts?user=' + user.id))
  .then((res) => res.json())
  .then((posts) => console.log(posts.length, 'posts'))
  .catch((err) => console.error(err));

Ein einzelnes .catch() am Ende behandelt eine Ablehnung aus jedem Schritt darüber, weshalb Verkettung das Verschachteln von Callbacks schlägt.

Staffelläufer auf einer Bahn, die den Staffelstab von einem Läufer zum nächsten übergeben
Wie Staffelläufer, die einen Stab übergeben, reicht eine Promise-Kette das Ergebnis jedes Schritts der Reihe nach an den nächsten Handler weiter.

Promises gemeinsam ausführen

Wenn Aufgaben nicht voneinander abhängen, starten Sie sie parallel und kombinieren die Ergebnisse. Vier statische Methoden decken die häufigsten Fälle ab:

  • Promise.all([...]) — wartet, bis alle erfüllt sind; wird abgelehnt, sobald eines abgelehnt wird. Gibt ein Array der Ergebnisse in Reihenfolge zurück.
  • Promise.allSettled([...]) — wartet, bis alle fertig sind, und wird nie abgelehnt; gibt ein Array von { status, value }- oder { status, reason }-Objekten zurück.
  • Promise.race([...]) — wird mit dem ersten Promise entschieden, das sich entscheidet, ob es erfüllt oder abgelehnt wird.
  • Promise.any([...]) — wird mit dem ersten Promise entschieden, das erfüllt wird; wird nur abgelehnt, wenn alle abgelehnt werden.
// Fetch three resources at once, fail if any fails
const [user, posts, prefs] = await Promise.all([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/posts').then((r) => r.json()),
  fetch('/api/prefs').then((r) => r.json()),
]);

// Same, but tolerate individual failures
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
results.forEach((r) => {
  if (r.status === 'fulfilled') console.log('ok', r.value);
  else console.warn('failed', r.reason);
});

Häufige Fallstricke

  • Das return vergessen. Wenn Sie innerhalb eines .then() ein Promise aufrufen, es aber nicht mit return zurückgeben, wartet die Kette nicht darauf — Sie verlieren Reihenfolge und Fehlerbehandlung.
  • Unbehandelte Ablehnungen. Ein Promise ohne .catch() (oder ein try/catch um await) erzeugt ein unhandledrejection. Behandeln Sie Fehler immer am Ende der Kette.
  • Callbacks und Promises vermischen. Rufen Sie resolve nicht zweimal auf und auch nicht sowohl resolve als auch reject — nur der erste Aufruf zählt; der Rest wird stillschweigend ignoriert.
  • Der Executor läuft sofort. Die Funktion, die Sie an new Promise() übergeben, läuft synchron, sofort — nur die .then()-Callbacks werden aufgeschoben.

Schnellreferenz

ZielMethode
Erfolg behandeln.then(onFulfilled)
Fehlschlag behandeln.catch(onRejected)
In beiden Fällen aufräumen.finally(fn)
Alle müssen gelingenPromise.all
Jedes Ergebnis sammelnPromise.allSettled
Erstes, das sich entscheidetPromise.race
Erstes, das gelingtPromise.any

Promises sind die Klempnerei unter async/await, das nur eine schönere Syntax über denselben Objekten ist. Sobald sich Verkettung und die vier Kombinatoren natürlich anfühlen, hört asynchrones JavaScript auf, einschüchternd zu sein.