Signatur
Beschreibung
Promise.withResolvers() ist eine statische Methode, die ein neues Promise erzeugt und die beiden Steuerfunktionen resolve und reject außerhalb der Executor-Funktion verfügbar macht. Das Ergebnis ist ein einfaches Objekt der Form { promise, resolve, reject }.
Die Methode ersetzt das bekannte „Deferred“-Muster, bei dem man die Resolver-Funktionen im Konstruktor in äußere Variablen kopiert hat:
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
Das ist praktisch, wenn das Erfüllen oder Ablehnen eines Promise von einem Ereignis abhängt, das an anderer Stelle im Code eintritt – etwa bei Event-Listenern, Stream-Callbacks, Warteschlangen, Nachrichten-Protokollen (WebSocket, postMessage) oder beim Umwandeln von Callback-APIs, deren Callback mehrfach bzw. verzögert aufgerufen wird.
Wie beim Konstruktor gilt: Ein Promise wird nur einmal abgeschlossen. Weitere Aufrufe von resolve oder reject werden stillschweigend ignoriert. Wird resolve mit einem anderen Promise oder Thenable aufgerufen, übernimmt das Promise dessen Zustand.
Rückgabewert
Ein einfaches Objekt mit den Eigenschaften promise (das neue, noch ausstehende Promise), resolve (erfüllt das Promise bzw. übernimmt den Zustand eines übergebenen Thenables) und reject (lehnt das Promise mit dem übergebenen Grund ab).
Beispiele
Grundprinzip: Promise von außen erfüllen
const { promise, resolve, reject } = Promise.withResolvers();
promise.then((wert) => console.log('Erfüllt mit:', wert));
// Irgendwo später im Code
setTimeout(() => resolve(42), 500);
Auf ein einmaliges DOM-Ereignis warten
const warteAufKlick = (element, timeoutMs = 5000) => {
const { promise, resolve, reject } = Promise.withResolvers();
const onClick = (event) => {
clearTimeout(timer);
resolve(event);
};
const timer = setTimeout(() => {
element.removeEventListener('click', onClick);
reject(new Error('Kein Klick innerhalb des Zeitlimits'));
}, timeoutMs);
element.addEventListener('click', onClick, { once: true });
return promise;
};
const button = document.querySelector('#bestaetigen');
try {
const event = await warteAufKlick(button);
console.log('Geklickt bei x =', event.clientX);
} catch (err) {
console.warn(err.message);
}
Node.js-Stream in ein Promise umwandeln
import { createReadStream } from 'node:fs';
const leseDatei = (pfad) => {
const { promise, resolve, reject } = Promise.withResolvers();
const teile = [];
createReadStream(pfad, { encoding: 'utf8' })
.on('data', (chunk) => teile.push(chunk))
.on('end', () => resolve(teile.join('')))
.on('error', reject);
return promise;
};
const inhalt = await leseDatei('./package.json');
console.log(JSON.parse(inhalt).name);
Anfrage-Antwort über WebSocket zuordnen
const offen = new Map();
let naechsteId = 1;
const socket = new WebSocket('wss://example.com/rpc');
socket.addEventListener('message', ({ data }) => {
const { id, result, error } = JSON.parse(data);
const eintrag = offen.get(id);
if (!eintrag) return;
offen.delete(id);
error ? eintrag.reject(new Error(error)) : eintrag.resolve(result);
});
const rufeAuf = (methode, params) => {
const id = naechsteId++;
const { promise, resolve, reject } = Promise.withResolvers();
offen.set(id, { resolve, reject });
socket.send(JSON.stringify({ id, methode, params }));
return promise;
};
// Nutzung (nach dem open-Event):
// const summe = await rufeAuf('addiere', [2, 3]);
// Wichtig · Fallstricke
- Kompatibilität: Neu in ES2024. Verfügbar ab Chrome/Edge 119, Firefox 121, Safari 17.4 und Node.js 22. Für ältere Umgebungen gibt es Polyfills (z. B.
core-js), oder man nutzt das klassische Muster mitnew Promise(). - Kein automatisches Abfangen von Fehlern: Beim Konstruktor wird eine im Executor geworfene Ausnahme automatisch zu einer Ablehnung. Bei
withResolvers()gibt es keinen Executor – Fehler in deinem Code musst du selbst abfangen und anrejectweiterreichen, sonst bleibt das Promise für immer ausstehend. - Hängende Promises: Wird weder
resolvenochrejectaufgerufen, wartet jedesawaitdarauf endlos. Für externe Ereignisse daher Zeitlimits oder einAbortSignaleinplanen und Listener aufräumen. - Nur einmal abschließbar: Nach dem ersten
resolve/rejectsind weitere Aufrufe wirkungslos und werfen keinen Fehler. - Kapselung: Gib nach außen möglichst nur
promiseweiter, nichtresolve/reject, damit fremder Code das Ergebnis nicht verändern kann. - Die Methode ist generisch: Mit
Promise.withResolvers.call(MeinePromiseUnterklasse)wird eine Instanz der Unterklasse erzeugt. Ein Aufruf ohne gültigen Konstruktor alsthiswirft einenTypeError.