Deferred promises

Promise.withResolvers()

Baseline newly available
  • Chrome119
  • Edge119
  • Firefox121
  • Safari17.4

Features it needs

A deferred is a promise with its resolve and reject pulled out, so something outside the executor can settle it later. Writing one by hand means declaring two variables, assigning them inside a new Promise, and trusting that the executor ran first. Promise.withResolvers() returns the promise and both functions in one object, which is the whole of what these packages do.

When this applies

Creating a promise that something else will resolve or reject later.

The native approach

const { promise, resolve, reject } = Promise.withResolvers();

socket.addEventListener("message", (event) => resolve(event.data));
socket.addEventListener("error", reject);

const firstMessage = await promise;

MDN reference

When the dependency is still right

An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.

  • The promise can settle inside its own executor. A deferred passed around is often a sign the async boundary sits in the wrong place, and starting the work inside new Promise needs neither this nor the package.
  • The package arrived as a transitive dependency. p-defer is pulled in by a good deal of the sindresorhus ecosystem, so removing your own import may not remove it from the tree.
  • You support browsers below Chrome 119, Firefox 121 or Safari 17.4, or a server runtime that predates it.
  • You need more than the three fields, such as a settled flag or a timeout, in which case you are writing a small class and the package is not what it costs.

Already checked by a linter

unicorn/prefer-promise-with-resolvers finds this mechanically, so it belongs in CI rather than in a review. Every rule a linter covers is on one page, with a config you can paste.

Packages this covers