Awesome
Promise.withResolvers
Note: this proposal is now at stage 4. See the spec PR here: https://github.com/tc39/ecma262/pull/3179
Status
Stage: 4
Champions:
- Peter Klecha (@peetklecha)
Authors:
- Peter Klecha (@peetklecha)
Stage 3 slides Stage 2 slides Stage 1 slides
Synopsis
When hand-rolling a Promise, the user must pass an executor callback which takes two arguments: a resolve function, which triggers resolution of the promise, and a reject function, which triggers rejection. This works well if the callback can embed a call to an asynchronous function which will eventually trigger the resolution or rejection, e.g., the registration of an event listener.
const promise = new Promise((resolve, reject) => {
asyncRequest(config, response => {
const buffer = [];
response.on('data', data => buffer.push(data));
response.on('end', () => resolve(buffer));
response.on('error', reason => reject(reason));
});
});
Often however developers would like to configure the promise's resolution and rejection behavior after instantiating it. Today this requires a cumbersome workaround to extract the resolve and reject functions from the callback scope:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
asyncRequest(config, response => {
const buffer = [];
response.on('callback-request', id => {
promise.then(data => callback(id, data));
});
response.on('data', data => buffer.push(data));
response.on('end', () => resolve(buffer));
response.on('error', reason => reject(reason));
});
Developers may also have requirements that necessitate passing resolve/reject to more than one caller, so they MUST implement it this way:
let resolve = () => { };
let reject = () => { };
function request(type, message) {
if (socket) {
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
socket.emit(type, message);
return promise;
}
return Promise.reject(new Error('Socket unavailable'));
}
socket.on('response', response => {
if (response.status === 200) {
resolve(response);
}
else {
reject(new Error(response));
}
});
socket.on('error', err => {
reject(err);
});
This is boilerplate code that is very frequently re-written by developers. This proposal simply seeks to add a static method, tentatively called withResolvers
, to the Promise
constructor which returns a promise along with its resolution and rejection functions conveniently exposed.
const { promise, resolve, reject } = Promise.withResolvers();
This method or something like it may be known to some committee members under the name defer
or deferred
, names also sometimes applied to utility functions in the ecosystem. This proposal adopts a more descriptive name for the benefit of users who may not be familiar with those historical functions.
Existing implementations
Libraries and applications continually re-invent this wheel. Below are just a handful of examples.
Library | Example |
---|---|
React | inline example |
Vue | inline example |
Axios | inline example |
TypeScript | utility |
Vite | inline example |
Deno stdlib | utility |