Promise.withResolvers()

The deferred pattern, standardised. Every codebase had a hand-rolled version of this; ES2024 finally shipped it.

Promise static methodES2024
Common call
const {promise, resolve, reject} = Promise.withResolvers()
Returns
an object with three properties
Replaces
let res; const p = new Promise(r => { res = r; });
Watch out
nothing settles it for you — a forgotten call hangs forever
Promise.withResolvers()
→ object

Common patterns

Bridge an event to a promise
The canonical use.
const {promise, resolve} = Promise.withResolvers();
socket.once("open", resolve);
await promise;
A queue of waiters
Hand out promises, settle them later.
const waiters = [];
function wait() {
  const d = Promise.withResolvers();
  waiters.push(d);
  return d.promise;
}
Prefer the constructor where it fits
If resolution happens inside, no escape is needed.
new Promise((res, rej) => fs.readFile(p, (e, d) => e ? rej(e) : res(d)));

Examples

1. Exactly three keys
Object.keys(Promise.withResolvers())
Returns
['promise', 'resolve', 'reject']
2. resolve is a function
typeof Promise.withResolvers().resolve
Returns
'function'
3. It works
const {promise, resolve} = Promise.withResolvers(); resolve("done"); await promise
Returns
'done'
4. The promise is a real one
Promise.withResolvers().promise instanceof Promise
Returns
true
5. Unsettled stays pending
await Promise.withResolvers().promise
Returns
never settles
6. The old idiom
let res; const p = new Promise(r => { res = r; });
Returns
the same thing, by hand

Pitfalls

1. Nothing settles it for you
The promise stays pending until you call resolve or reject. A code path that returns early without settling leaves every awaiter hanging silently — no error, no timeout. This is the cost of escaping the constructor.
Hangs
const {promise} = Promise.withResolvers();
await promise;
never continues
Always settle
try { doWork(resolve); } catch (e) { reject(e); }
settles either way
2. It loses the constructor safety net
An exception thrown inside the Promise constructor executor automatically rejects the promise. There is no executor here, so a throw in your surrounding code leaves the promise pending forever unless you reject it yourself.
Pending forever
const {promise, resolve} = Promise.withResolvers();
risky();   // throws
resolve(1);
promise never settles
Reject on throw
try { risky(); resolve(1); } catch (e) { reject(e); }
rejected
3. Settling twice is silently ignored
The first call wins and later ones do nothing — no error, no warning. That makes double-resolution bugs invisible: a race between two code paths produces whichever result arrived first, with no indication that the other happened.
Second ignored
resolve("a");
resolve("b");
await promise
'a'
Guard if it matters
let settled = false;
const once = v => { if (!settled) { settled = true; resolve(v); } };
explicit
4. ES2024 — check your runtime
Node 22+ and 2024-era browsers. The hand-rolled version is three lines and works everywhere, which is exactly why this took so long to standardise.
Missing
Promise.withResolvers()
TypeError: Promise.withResolvers is not a function
Roll it yourself
function withResolvers() {
  let resolve, reject;
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
  return {promise, resolve, reject};
}
equivalent

When to use

Use it
  • Bridging an event, callback or message into a promise
  • Handing a promise to a caller and settling it from elsewhere
  • A queue of waiters settled by a later signal
  • Replacing a hand-rolled deferred helper
Reach for something else
  • Resolution happens inside one function → the Promise constructor
  • You already have the value → Promise.resolve
  • You can await the source directly → do that
  • Targeting runtimes older than 2024 → the three-line equivalent

Notes

Complexity
O(1)
Return
A plain object with three own properties; the promise is a normal Promise
CPython impl
V8: Builtins-promise-withresolvers
Memory
Allocates the promise and the wrapper object
Thread-safe
Single-threaded; resolve and reject may be called from any later tick

FAQ

Because the only thing observable without waiting is that the returned object has three fixed keys — a constant, not a computation. Anything more requires the promise to settle, which happens asynchronously. The examples above were run in a real runtime.

History

ES2015
Promise shipped with only the constructor; the deferred pattern was written by hand everywhere.
ES2024
Promise.withResolvers added, standardising it.