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:
| Property | Type | Description |
|---|---|---|
promise | Promise | A new promise in the pending state |
resolve | function | Resolves promise; behavior matches the resolver handed to the Promise executor |
reject | function | Rejects 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
| Specification | Status |
|---|---|
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.
| Engine | Version | Date |
|---|---|---|
| Chrome | 119 | 2023-10-31 |
| Edge (Chromium) | 119 | 2023-11-02 |
| Firefox | 121 | 2023-12-19 |
| Safari / iOS Safari | 17.4 | 2024-03-05 |
| Node.js | 22.0.0 | 2024-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— thePromiseconstructor and its prototype methodsPromise.resolve()—Promise.resolve(), the static that wraps a value in a resolved promisePromise.reject()—Promise.reject(), the static that wraps a reason in a rejected promise