🪟 Windows TippsWinZip(17.09.2026 um 08:30 Uhr)
🪟 Windows ServerDomänen-Trust weg nach Windows-Update - IT-Administrator.de(17.09.2026 um 08:17 Uhr)
🪟 Windows TippsWinZip(17.09.2026 um 08:30 Uhr)
🪟 Windows ServerDomänen-Trust weg nach Windows-Update - IT-Administrator.de(17.09.2026 um 08:17 Uhr)
🔧 Programmierung 🕛 vor 1 Monat 5 Min Lesezeit
0

You keep escaping the Promise constructor. `Promise.withResolvers` does it right.

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Every JavaScript developer has written this at least once:




CODE
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});

// Later…
eventBus.on('done', () => resolve(result));






You need to trigger the Promise from outside the constructor — from an event listener, a callback, or a message handler. So you capture resolve and reject by leaking them into the outer scope. It works. It also looks wrong every time you write it, because you're manually escaping a closure that wasn't designed to be escaped.



ES2024 added the right tool for this: Promise.withResolvers.






What Promise.withResolvers returns



Instead of constructing a Promise and escaping its callbacks, you call a static method that hands you all three pieces at once:




CODE
const { promise, resolve, reject } = Promise.withResolvers();






promise is a regular Promise. resolve and reject are its resolution functions, already sitting in your scope. No closure escape needed. The pattern that used to take four lines now takes one.



The old pattern and the new one behave identically — the same microtask timing, the same error propagation, the same .then/.catch interface. The difference is that the intent is now explicit: you're creating a deferred Promise on purpose, not as a workaround.






The event-to-Promise bridge



The clearest use case is wrapping event-based APIs in a Promise interface. Before:




CODE
function waitForOpen(socket) {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
socket.addEventListener('open', () => resolve());
socket.addEventListener('error', (e) => reject(e));
return promise;
}






After:




CODE
function waitForOpen(socket) {
const { promise, resolve, reject } = Promise.withResolvers();
socket.addEventListener('open', () => resolve());
socket.addEventListener('error', (e) => reject(e));
return promise;
}






Same logic, much less ceremony. The function returns a Promise that resolves or rejects based on the socket's events — which is exactly what you'd read from the clean version. The old version made you parse around the escape boilerplate to get there.






Async queues and message passing



A place where deferred Promises appear repeatedly is async queues — structures where a consumer awaits the next item and a producer calls push later:




CODE
function createQueue() {
const pending = [];
let deferred = Promise.withResolvers();

return {
push(item) {
pending.push(item);
deferred.resolve();
deferred = Promise.withResolvers();
},

async *[Symbol.asyncIterator]() {
while (true) {
await deferred.promise;
while (pending.length) {
yield pending.shift();
}
}
},
};
}

const queue = createQueue();

// Consumer
(async () => {
for await (const item of queue) {
console.log('received:', item);
}
})();

// Producer (elsewhere)
queue.push('hello');
queue.push('world');






Each call to Promise.withResolvers() creates a fresh gate. The consumer waits on it; the producer resolves it when there's something to read. Without withResolvers, every one of those gates would need the escape boilerplate. With it, the queue logic reads straight through.






Controlled flushing: waiting for a batch



Another pattern: collecting items for a batch operation and resolving all waiters at once when the batch fires.




CODE
class Batcher {
#items = [];
#deferred = Promise.withResolvers();

add(item) {
this.#items.push(item);
return this.#deferred.promise;
}

async flush() {
const batch = this.#items.splice(0);
const { resolve } = this.#deferred;
this.#deferred = Promise.withResolvers();
const results = await this.#processBatch(batch);
resolve(results);
return results;
}
}






Callers await batcher.add(item) and get the result when the batch flushes — whether that's triggered by a timer, a size limit, or explicit user action. Each flush resets the gate cleanly with a single Promise.withResolvers() call.






When not to reach for it



Deferred Promises are a tool for specific situations, not a general replacement for the constructor form. If you control both the setup and the resolution — fetch, fs.readFile, any async API you're directly awaiting — use async/await or the Promise constructor directly. Deferred patterns are specifically for when setup and resolution happen in different execution contexts that can't share a callback cleanly.



One concrete anti-pattern: if you find yourself calling resolve synchronously inside the same function that created the deferred, you probably just wanted new Promise(res => res(value)) — which is Promise.resolve(value).






TypeScript support



TypeScript added Promise.withResolvers in version 5.4. The return type is typed correctly — PromiseWithResolvers<T> carries the type through:




CODE
const { promise, resolve, reject } = Promise.withResolvers<string>();

resolve('hello'); // ✅ string
resolve(42); // ❌ type error






If you're on an older TS target, add "ES2024" to lib in your tsconfig.json.






Browser support



Promise.withResolvers is Baseline 2024: Chrome 119, Firefox 121, Safari 17.4, Node.js 22. If you're targeting modern environments, it's available with no polyfill. For older targets, the old escape pattern remains correct — withResolvers is syntax sugar for something you can always write by hand.






The takeaway



Search your codebase for the pattern let resolve or let reject followed by a new Promise. Every one of those is a deferred Promise written the hard way. Promise.withResolvers() names the pattern, eliminates the escape boilerplate, and makes the intent readable at a glance. The result is the same; the code is cleaner by a visible margin.






Thanks for reading! Let's stay connected:



Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Windows 11 startet nicht: So findet ihr die Ursache und behebt sie
1 Quelle
Belegen Sie die Copilot-Taste neu und starten Sie damit Ihre Lieblings-App
1 Quelle
WinZip
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten You keep escaping the Promise constructor. `Promise.withResolvers` does it right.

Thematisch verwandte Begriffe: keep, escaping, Promise, constructor · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...