jsguides

Promise.withResolvers()

Promise.withResolvers()

Promise.withResolvers() is a static method on the Promise constructor that returns a new pending promise alongside its resolve and reject functions. It removes the boilerplate of hoisting those functions out of a Promise executor, and it shipped in ECMAScript 2024.

The pattern it replaces has been in JavaScript since the early days of promises. The old way is fine but slightly noisy. The new way is shorter, and the resolver functions stay in scope so you can keep using them.

Syntax

Promise.withResolvers()

Call it on the Promise constructor, not on a Promise instance. somePromise.withResolvers() throws TypeError: somePromise.withResolvers is not a function.

Parameters

None.

Return value

A plain object (not a Promise instance) with three own, enumerable, writable, configurable data properties:

PropertyTypeDescription
promisePromiseA new promise in the pending state
resolvefunctionResolves promise; behavior matches the resolver handed to the Promise executor
rejectfunctionRejects promise; behavior matches the rejecter handed to the Promise executor

The two functions come from the same internal capability that backs the Promise constructor’s executor, so the standard rules apply: only the first resolve or reject call wins, non-thenable values pass through, thenables are adopted, and a late settlement still reaches any await, .then, or .catch already attached to promise.

Destructuring is the only useful thing to do with the returned object. You cannot .then() on it.

Examples

Settling from an event listener

The classic use case is when settlement has to happen from somewhere that is not the Promise executor, like an event handler. Without withResolvers you hoist the functions; with it, you destructure them in one line.

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

button.addEventListener("click", () => resolve("clicked"), { once: true });
button.addEventListener("contextmenu", (e) => {
  e.preventDefault();
  reject(new Error("cancelled"));
}, { once: true });

const result = await promise; // "clicked"

resolve and reject live in the same scope as promise, so the listeners can call them whenever they fire. Pending consumers attached later still receive the settlement.

Before and after

If you have ever written code that looked like the snippet below, withResolvers is what you would write today.

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

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

Same behavior, no let declarations, no inner closure, no chance of capturing the wrong reference if the executor throws.

Reusing the resolver pair

Because the destructured bindings are plain variables, you can reassign them to a fresh tuple whenever you need to settle a new promise. This is the pattern that turns a Node stream into an async iterable.

async function* readableToAsyncIterable(stream) {
  let { promise, resolve, reject } = Promise.withResolvers();
  stream.on("error", (error) => reject(error));
  stream.on("end", () => resolve());
  stream.on("readable", () => resolve());

  while (stream.readable) {
    await promise;
    let chunk;
    while ((chunk = stream.read())) {
      yield chunk; // chunk: Buffer
    }
    ({ promise, resolve, reject } = Promise.withResolvers());
  }
}

The event listeners stay attached for the whole stream, and each batch uses its own resolver pair. That reuse is the real reason the method exists.

Specifications

SpecificationStatus
ECMA-262, Promise.withResolvers (sec-promise.withresolvers)ES2024 (Stage 4, merged via tc39/ecma262#3179)

The TC39 proposal was championed by Peter Klecha and reached Stage 4 in November 2023.

Browser compatibility

Promise.withResolvers() is part of Baseline 2024, newly available across major engines. Safari 17.4 was the last browser to ship it on 2024-03-05.

EngineVersionDate
Chrome1192023-10-31
Edge (Chromium)1192023-11-02
Firefox1212023-12-19
Safari / iOS Safari17.42024-03-05
Node.js22.0.02024-04-24

Node.js 21.7.1 and later shipped the method behind the --js-promise-withresolvers flag. That flag was removed once V8 12.4 went stable in Node 22.

See also

  • Promise — the Promise constructor and its prototype methods
  • Promise.resolve()Promise.resolve(), the static that wraps a value in a resolved promise
  • Promise.reject()Promise.reject(), the static that wraps a reason in a rejected promise