Signatur
Beschreibung
Ein Promise ist ein Stellvertreter für einen Wert, der zum Zeitpunkt der Erzeugung des Promise nicht notwendigerweise bekannt ist. Er ermöglicht es, Handler mit dem zukünftigen Erfolgswert oder dem Fehlergrund einer asynchronen Aktion zu verknüpfen. Dadurch können asynchrone Methoden Werte wie synchrone Methoden zurückgeben: Anstatt sofort den endgültigen Wert zurückzugeben, gibt die asynchrone Methode ein Promise zurück, den Wert zu einem späteren Zeitpunkt zu liefern.
Ein Promise befindet sich in einem der folgenden Zustände:
- pending: Anfangszustand, weder fulfilled noch rejected.
- fulfilled: bedeutet, dass die Operation erfolgreich abgeschlossen wurde.
- rejected: bedeutet, dass die Operation fehlgeschlagen ist.
Der eventuelle Zustand eines pending Promise kann entweder fulfilled mit einem Wert oder rejected mit einem Grund (Fehler) sein. Wenn eine dieser Optionen eintritt, werden die zugehörigen Handler, die von der then-Methode eines Promise in eine Warteschlange gestellt wurden, aufgerufen. Wenn das Promise bereits fulfilled oder rejected wurde, wenn ein entsprechender Handler angehängt wird, wird der Handler aufgerufen, sodass es keine Race-Condition zwischen dem Abschluss einer asynchronen Operation und dem Anhängen ihrer Handler gibt.
Ein Promise wird als settled bezeichnet, wenn es entweder fulfilled oder rejected ist, aber nicht pending.
Man hört auch den Begriff resolved im Zusammenhang mit Promises — dies bedeutet, dass das Promise settled ist oder „festgelegt" wurde, um dem eventuellen Zustand eines anderen Promise zu entsprechen, und weiteres Resolving oder Rejecting keine Wirkung hat. Umgangssprachlich sind „resolved" Promises oft äquivalent zu „fulfilled" Promises, aber wie in „States and fates" dargestellt, können resolved Promises auch pending oder rejected sein.
Promise selbst hat kein First-Class-Protokoll für Abbrüche, aber Sie können möglicherweise die zugrunde liegende asynchrone Operation direkt abbrechen, typischerweise mit AbortController.
Verkettete Promises
Die Promise-Methoden then(), catch() und finally() werden verwendet, um weitere Aktionen mit einem Promise zu verknüpfen, das settled wird. Die Methode then() nimmt bis zu zwei Argumente entgegen; das erste Argument ist eine callback-Funktion für den fulfilled-Fall des Promise, und das zweite Argument ist eine callback-Funktion für den rejected-Fall. Die Methoden catch() und finally() rufen intern then() auf und machen die Fehlerbehandlung weniger umständlich. Da diese Methoden Promises zurückgeben, können sie verkettet werden.
Wir verwenden folgende Terminologie: initial promise ist das Promise, auf dem then aufgerufen wird; new promise ist das Promise, das von then zurückgegeben wird. Die beiden an then übergebenen callbacks werden fulfillment handler bzw. rejection handler genannt.
Der settled-Zustand des initial promise bestimmt, welcher Handler ausgeführt wird.
- Ist das initial promise fulfilled, wird der fulfillment handler mit dem fulfillment-Wert aufgerufen.
- Ist das initial promise rejected, wird der rejection handler mit dem rejection-Grund aufgerufen.
Der Abschluss des Handlers bestimmt den settled-Zustand des new promise.
- Gibt der Handler einen thenable-Wert zurück, wird das new promise im gleichen Zustand wie der zurückgegebene Wert settled.
- Gibt der Handler einen nicht-thenable-Wert zurück, wird das new promise mit dem zurückgegebenen Wert fulfilled.
- Wirft der Handler einen Fehler, wird das new promise mit dem geworfenen Fehler rejected.
- Hat das initial promise keinen entsprechenden Handler angehängt, wird das new promise in den gleichen Zustand wie das initial promise wechseln — das heißt, ohne rejection handler bleibt ein rejected Promise mit demselben Grund rejected.
JavaScript verwaltet eine Job-Queue. Jedes Mal wählt JavaScript einen Job aus der Queue und führt ihn vollständig aus. Die Jobs werden durch den Executor des Promise()-Konstruktors, die an then übergebenen Handler oder jede Plattform-API definiert, die ein Promise zurückgibt. Die Promises in einer Kette repräsentieren die Abhängigkeitsbeziehung zwischen diesen Jobs. Wenn ein Promise settled wird, werden die zugehörigen Handler am Ende der Job-Queue hinzugefügt.
Ein Promise kann an mehr als einer Kette teilnehmen. Eine Aktion kann einem bereits settled Promise zugewiesen werden. In diesem Fall wird die Aktion sofort am Ende der Job-Queue hinzugefügt und ausgeführt, sobald alle bestehenden Jobs abgeschlossen sind. Dies garantiert, dass Promise-Aktionen asynchron sind.
Thenables
Das JavaScript-Ökosystem hatte lange vor der Aufnahme in die Sprache mehrere Promise-Implementierungen. Obwohl sie intern unterschiedlich dargestellt werden, implementieren alle Promise-ähnlichen Objekte mindestens das Thenable-Interface. Ein thenable implementiert die .then()-Methode, die mit zwei callbacks aufgerufen wird: einem für den Fall, dass das Promise fulfilled ist, und einem für den Fall, dass es rejected ist. Promises sind ebenfalls thenables.
Um mit bestehenden Promise-Implementierungen zusammenarbeiten zu können, erlaubt die Sprache die Verwendung von thenables anstelle von Promises. Zum Beispiel wird Promise.resolve nicht nur Promises resolven, sondern auch thenables verfolgen.
Promise Concurrency
Die Klasse Promise bietet vier wichtige statische Methoden zur Erleichterung der Concurrency asynchroner Tasks:
Promise.all(): Fulfills, wenn alle Promises fulfilled werden; rejects, wenn irgendeines der Promises rejected wird.Promise.allSettled(): Fulfills, wenn alle Promises settled sind.Promise.any(): Fulfills, wenn irgendeines der Promises fulfilled wird; rejects, wenn alle Promises rejected werden.Promise.race(): Settles, wenn irgendeines der Promises settled wird.
Alle diese Methoden nehmen ein Iterable von Promises (genauer gesagt thenables) und geben ein neues Promise zurück. Sie unterstützen alle Subclassing, was bedeutet, dass sie auf Subklassen von Promise aufgerufen werden können, und das Ergebnis wird ein Promise des Subklassentyps sein.
Diese Methoden hängen Handler an jedes Input-Promise mittels then(). Selbst wenn das resultierende Promise frühzeitig settled ist (etwa wenn ein Input in Promise.race() settled), werden die anderen Handler nicht entfernt. Wiederholtes Übergeben desselben pending Promise an Concurrency-Methoden kann Handler ansammeln, selbst wenn diese Handler nie verwendet werden.
Promises bieten keine Möglichkeit, diese Handler abzumelden; sie bleiben angehängt, während das Input-Promise pending und erreichbar ist. Wo möglich, brechen Sie die zugrunde liegende Operation mit einem AbortSignal ab, wenn das pending Promise nicht mehr nützlich ist.
Beachten Sie, dass JavaScript von Natur aus single-threaded ist, sodass zu einem bestimmten Zeitpunkt nur eine Aufgabe ausgeführt wird, obwohl die Kontrolle zwischen verschiedenen Promises wechseln kann, was die Ausführung der Promises concurrent erscheinen lässt. Parallele Ausführung in JavaScript kann nur durch Worker-Threads erreicht werden.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $executor Pflicht | function | Eine Funktion, die sofort beim Erstellen des Promise aufgerufen wird. Sie erhält zwei Argumente: resolve(value) zum Erfüllen und reject(reason) zum Ablehnen des Promise. Exceptions innerhalb des Executors werden automatisch als Rejection behandelt. |
Rückgabewert
Beispiele
Resolved mit anderem Promise
new Promise((resolveOuter) => {
resolveOuter(
new Promise((resolveInner) => {
setTimeout(resolveInner, 1000);
}),
);
});
Promise-Kette mit then/catch
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 300);
});
myPromise
.then(handleFulfilledA, handleRejectedA)
.then(handleFulfilledB, handleRejectedB)
.then(handleFulfilledC, handleRejectedC);
Fehlerbehandlung am Ende der Kette
myPromise
.then(handleFulfilledA)
.then(handleFulfilledB)
.then(handleFulfilledC)
.catch(handleRejectedAny);
Kette mit Arrow-Funktionen
myPromise
.then((value) => `${value} and bar`)
.then((value) => `${value} and bar again`)
.then((value) => `${value} and again`)
.then((value) => `${value} and again`)
.then((value) => {
console.log(value);
})
.catch((err) => {
console.error(err);
});
Mehrere Handler auf demselben Promise
const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2);
Asynchrone Ausführung von Handlern
const promiseA = new Promise((resolve, reject) => {
resolve(777);
});
// At this point, "promiseA" is already settled.
promiseA.then((val) => console.log("asynchronous logging has val:", val));
console.log("immediate logging");
// produces output in this order:
// immediate logging
// asynchronous logging has val: 777
Thenable-Objekt
// This is not a Promises/A+ compliant thenable! It calls onFulfilled
// synchronously. For demonstration only.
const thenable = {
then(onFulfilled, onRejected) {
onFulfilled({
// The thenable is fulfilled with another thenable
then(onFulfilled, onRejected) {
onFulfilled(42);
},
});
},
};
Promise.resolve(thenable); // A promise fulfilled with 42
Handler-Ansammlung bei pending Promises
const pendingPromise = new Promise(() => {});
for (let i = 0; i < 1000; i++) {
await Promise.race([Promise.resolve(0), pendingPromise]);
}
// All tasks have completed, but pendingPromise retains the
// handlers attached by all 1000 races.
Einfaches Beispiel
const myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously
// was successful, and reject(...) when it failed.
setTimeout(() => {
resolve("Success!"); // Yay! Everything went well!
}, 250);
});
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log(`Yay! ${successMessage}`);
});
Beispiel mit verschiedenen Situationen
// To experiment with error handling, "threshold" values cause errors randomly
const THRESHOLD_A = 8; // can use zero 0 to guarantee error
function tetheredGetNumber(resolve, reject) {
setTimeout(() => {
const randomInt = Date.now();
const value = randomInt % 10;
if (value < THRESHOLD_A) {
resolve(value);
} else {
reject(new RangeError(`Too large: ${value}`));
}
}, 500);
}
function determineParity(value) {
const isOdd = value % 2 === 1;
return { value, isOdd };
}
function troubleWithGetNumber(reason) {
const err = new Error("Trouble getting number", { cause: reason });
console.error(err);
throw err;
}
function promiseGetWord(parityInfo) {
return new Promise((resolve, reject) => {
const { value, isOdd } = parityInfo;
if (value >= THRESHOLD_A - 1) {
reject(new RangeError(`Still too large: ${value}`));
} else {
parityInfo.wordEvenOdd = isOdd ? "odd" : "even";
resolve(parityInfo);
}
});
}
new Promise(tetheredGetNumber)
.then(determineParity, troubleWithGetNumber)
.then(promiseGetWord)
.then((info) => {
console.log(`Got: ${info.value}, ${info.wordEvenOdd}`);
return info;
})
.catch((reason) => {
if (reason.cause) {
console.error("Had previously handled error");
} else {
console.error(`Trouble with promiseGetWord(): ${reason}`);
}
})
.finally((info) => console.log("All done"));
Fortgeschrittenes Beispiel (JavaScript)
"use strict";
let promiseCount = 0;
function testPromise() {
const thisPromiseCount = ++promiseCount;
const log = document.getElementById("log");
// begin
log.insertAdjacentHTML("beforeend", `${thisPromiseCount}) Started<br>`);
// We make a new promise: we promise a numeric count of this promise,
// starting from 1 (after waiting 3s)
const p1 = new Promise((resolve, reject) => {
// The executor function is called with the ability
// to resolve or reject the promise
log.insertAdjacentHTML(
"beforeend",
`${thisPromiseCount}) Promise constructor<br>`,
);
// This is only an example to create asynchronism
setTimeout(
() => {
// We fulfill the promise
resolve(thisPromiseCount);
},
Math.random() * 2000 + 1000,
);
});
// We define what to do when the promise is resolved with the then() call,
// and what to do when the promise is rejected with the catch() call
p1.then((val) => {
// Log the fulfillment value
log.insertAdjacentHTML("beforeend", `${val}) Promise fulfilled<br>`);
}).catch((reason) => {
// Log the rejection reason
console.log(`Handle rejected promise (${reason}) here.`);
});
// end
log.insertAdjacentHTML("beforeend", `${thisPromiseCount}) Promise made<br>`);
}
const btn = document.getElementById("make-promise");
btn.addEventListener("click", testPromise);
Ein Bild mit XHR laden
function imgLoad(url) {
// Create new promise with the Promise() constructor;
// This has as its argument a function with two parameters, resolve and reject
return new Promise((resolve, reject) => {
// XHR to load an image
const request = new XMLHttpRequest();
request.open("GET", url);
request.responseType = "blob";
// When the request loads, check whether it was successful
request.onload = () => {
if (request.status === 200) {
// If successful, resolve the promise by passing back the request response
resolve(request.response);
} else {
// If it fails, reject the promise with an error message
reject(
Error(
`Image didn't load successfully; error code: + ${request.statusText}`,
),
);
}
};
// Handle network errors
request.onerror = () => reject(new Error("There was a network error."));
// Send the request
request.send();
});
}
// Get a reference to the body element, and create a new image object
const body = document.querySelector("body");
const myImage = new Image();
const imgUrl =
"https://mdn.github.io/shared-assets/images/examples/round-balloon.png";
// Call the function with the URL we want to load, then chain the
// promise then() method with two callbacks
imgLoad(imgUrl).then(
(response) => {
// The first runs when the promise resolves, with the request.response
// specified within the resolve() method.
const imageURL = URL.createObjectURL(response);
myImage.src = imageURL;
body.appendChild(myImage);
},
(error) => {
// The second runs when the promise
// is rejected, and logs the Error specified with the reject() method.
console.log(error);
},
);