Signatur
Beschreibung
Promise[Symbol.species] ist eine statische Zugriffseigenschaft, die nur einen Getter hat. Standardmäßig gibt sie einfach this zurück, also den Konstruktor, auf dem sie abgefragt wird. Bei Promise selbst ist das Promise, bei einer Unterklasse class MyPromise extends Promise {} ist es MyPromise.
Die Instanzmethoden Promise.prototype.then(), catch() und finally() erzeugen ihr Rückgabe-Promise nicht fest mit Promise. Sie ermitteln den Konstruktor über den internen Algorithmus SpeciesConstructor: Zuerst lesen sie this.constructor, dann dessen [Symbol.species]. Deshalb bleiben Ketten wie subPromise.then(...).then(...) automatisch Instanzen der Unterklasse, und eigene Zusatzmethoden stehen in der ganzen Kette zur Verfügung.
Wenn Sie in einer Unterklasse static get [Symbol.species]() überschreiben, legen Sie fest, welcher Konstruktor für abgeleitete Promises verwendet wird. Das ist sinnvoll, wenn verkettete Promises bewusst wieder normale Promise-Objekte sein sollen, etwa damit Sonderverhalten (Logging, Abbruch-Logik, Zusatzzustand) nicht an nachgelagerten Code weitergegeben wird.
Die statischen Methoden Promise.resolve(), Promise.all(), Promise.race(), Promise.allSettled(), Promise.any() und Promise.withResolvers() werten Symbol.species nicht aus. Sie verwenden direkt this als Konstruktor.
Rückgabewert
this, auf dem der Getter aufgerufen wurde, also normalerweise Promise oder die jeweilige Unterklasse. Unterklassen können einen anderen Konstruktor zurückgeben.Beispiele
Standardwert bei Promise und Unterklassen
class MyPromise extends Promise {}
console.log(Promise[Symbol.species] === Promise);
console.log(MyPromise[Symbol.species] === MyPromise);
// then() nutzt species, die Kette bleibt MyPromise
const p = MyPromise.resolve(42).then((v) => v * 2);
console.log(p instanceof MyPromise);
Species überschreiben: Ketten liefern wieder normale Promises
class LoggingPromise extends Promise {
// then/catch/finally sollen normale Promises erzeugen
static get [Symbol.species]() {
return Promise;
}
}
const base = LoggingPromise.resolve('Daten');
const chained = base.then((v) => v.toUpperCase());
console.log(base instanceof LoggingPromise); // resolve() nutzt this
console.log(chained instanceof LoggingPromise); // then() nutzt species
console.log(chained instanceof Promise);
chained.then(console.log);
Praxis: Zusatzmethode bleibt in der ganzen Kette verfügbar
class TimedPromise extends Promise {
timeout(ms) {
// Promise.race auf dem Konstruktor der Instanz -> wieder TimedPromise
return this.constructor.race([
this,
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout nach ${ms} ms`)), ms)
),
]);
}
}
const loadUser = () =>
new TimedPromise((resolve) => setTimeout(() => resolve({ name: 'Ada' }), 500));
const main = async () => {
try {
// .then() liefert dank species ein TimedPromise, timeout() existiert also
const name = await loadUser()
.then((user) => user.name)
.timeout(100);
console.log(name);
} catch (err) {
console.error(err.message);
}
};
main();
Species auswerten wie then() es intern tut
const speciesOf = (promise) => {
const C = promise.constructor;
if (C === undefined) return Promise;
const S = C[Symbol.species];
return S ?? Promise; // undefined/null -> Standard-Promise
};
class A extends Promise {}
class B extends Promise {
static get [Symbol.species]() { return Promise; }
}
console.log(speciesOf(A.resolve()).name);
console.log(speciesOf(B.resolve()).name);
// Wichtig · Fallstricke
- Nur Getter, kein Setter: Mit
MyPromise[Symbol.species] = Promiselässt sich der Wert nicht ändern. Im nicht-strikten Modus schlägt die Zuweisung stillschweigend fehl, im strikten Modus (Klassen, ES-Module) wirft sie einenTypeError. Verwenden Sie stattdessenstatic get [Symbol.species]()oderObject.defineProperty. - Gilt nicht für statische Methoden:
Promise.all(),race(),resolve()und Co. verwendenthisund ignorierenSymbol.species. - Anforderungen an den Konstruktor: Der zurückgegebene Wert muss ein Konstruktor sein, der sich wie
Promisemit einer Executor-Funktion aufrufen lässt. Andernfalls werfenthen()und Co. einenTypeError. Wenn[Symbol.species]undefinedodernullliefert, wirdPromiseverwendet. - Unterklassen-Konstruktoren: Wer den Konstruktor einer Unterklasse überschreibt, muss den Executor an
super(executor)weiterreichen, sonst schlagen die intern über species erzeugten Promises fehl. - Performance und Sicherheit: Die dynamische Suche über
constructorund species macht Engine-Optimierungen schwerer und war in der Vergangenheit eine Angriffsfläche. In TC39 wird seit Längerem diskutiert, die Unterstützung für species einzuschränken. Verlassen Sie sich in neuem Code möglichst nicht darauf. - Kompatibilität: Wird in allen modernen Browsern und seit Node.js 6.5 unterstützt (Baseline). Im Internet Explorer fehlt die Eigenschaft.