Deferred promises
Promise.withResolvers()
Chrome119
Edge119
Firefox121
Safari17.4
Features it needs
- Promise.withResolvers()Newly available
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;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.