GitHub - tc39/proposal-promise-with-resolvers (original) (raw)
Promise.withResolvers
Note: this proposal is now at stage 4. See the spec PR here: tc39/ecma262#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 |