For AI agents: the complete documentation index is available at /tc39-atlas/en/llms.txt, the full documentation bundle is available at /tc39-atlas/en/llms-full.txt, and this page is available as Markdown at /tc39-atlas/en/proposals/year/2024/proposal-promise-with-resolvers.md.
  • English
  • Promise.withResolvers S4

    Proposal details
    Proposal overview

    The proposal addresses boilerplate code required to extract resolve and reject functions when creating a Promise manually. It adds a static method Promise.withResolvers() that returns an object containing the promise and its resolve/reject functions.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    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