Start · Sprachen · JavaScript · Referenz · Promise[Symbol.species]

Promise[Symbol.species]

Eigenschaft

Statische Getter-Eigenschaft, die den Konstruktor liefert, mit dem <code>then()</code>, <code>catch()</code> und <code>finally()</code> neue Promises erzeugen. Standardmäßig ist das <code>this</code>.

seit JavaScript ES2015 Kategorie: method-property

Signatur

static get Promise[Symbol.species]

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

Typ
Function (PromiseConstructor)
Beschreibung
Den Wert von 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);
true true true

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);
true false true DATEN

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();
Timeout nach 100 ms

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);
A Promise

// Wichtig · Fallstricke

  • Nur Getter, kein Setter: Mit MyPromise[Symbol.species] = Promise lässt sich der Wert nicht ändern. Im nicht-strikten Modus schlägt die Zuweisung stillschweigend fehl, im strikten Modus (Klassen, ES-Module) wirft sie einen TypeError. Verwenden Sie stattdessen static get [Symbol.species]() oder Object.defineProperty.
  • Gilt nicht für statische Methoden: Promise.all(), race(), resolve() und Co. verwenden this und ignorieren Symbol.species.
  • Anforderungen an den Konstruktor: Der zurückgegebene Wert muss ein Konstruktor sein, der sich wie Promise mit einer Executor-Funktion aufrufen lässt. Andernfalls werfen then() und Co. einen TypeError. Wenn [Symbol.species] undefined oder null liefert, wird Promise verwendet.
  • 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 constructor und 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.