Home

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:

Authors:

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.

LibraryExample
Reactinline example
Vueinline example
Axiosinline example
TypeScriptutility
Viteinline example
Deno stdlibutility