Skip to content

Instantly share code, notes, and snippets.

@lmammino
Created July 18, 2023 17:57
Show Gist options
  • Select an option

  • Save lmammino/ef121da874a80d657379a1cd64bf8166 to your computer and use it in GitHub Desktop.

Select an option

Save lmammino/ef121da874a80d657379a1cd64bf8166 to your computer and use it in GitHub Desktop.
Promise.withResolvers() polyfill
if (typeof Promise.withResolvers === 'undefined') {
Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
}
// Usage:
// const { promise, resolve, reject } = Promise.withResolvers()
// console.log(promise, resolve, reject) // Promise { <pending> } [Function (anonymous)] [Function (anonymous)]
// ... Do something async and then call resolve or reject!
@aralroca
Copy link

aralroca commented Sep 3, 2024

It depends on the runtime, I had issues with Node 20

@lmammino
Copy link
Author

lmammino commented Sep 3, 2024

It depends on the runtime, I had issues with Node 20

I just tested the snippet above with Node 20 and, as far as I can tell, it behaves as expected.

I hope the issue you describe is not runtime-dependent, because that would mean that the specific runtime is not spec-compliant.

Do you have an example I can try to run that illustrates the problem?

@aralroca
Copy link

aralroca commented Sep 3, 2024

@lmammino this is not working:

Object.assign(Promise, {
	withResolvers: () => {
		let resolve;
		let reject;

		const promise = new Promise((resolve, reject) => {
			resolve = resolve;
			reject = reject;
		});

		return {
			promise,
			resolve,
			reject
		};
}});

const a = Promise.withResolvers()
a.promise.then(() => console.log('resolved'))
a.resolve();

With this error:

VM717:20 Uncaught TypeError: a.resolve is not a function
    at <anonymous>:20:3

But I see that it is because of the name that I am reassigning the variable inside the function, not the one outside. It's totally my fault. Sorry about that.

@Stadly
Copy link

Stadly commented Nov 24, 2024

A TypeScript version:

if (typeof Promise.withResolvers === "undefined") {
  Promise.withResolvers = <T>() => {
    let resolve: (value: T | PromiseLike<T>) => void;
    let reject: (reason?: unknown) => void;
    const promise = new Promise<T>((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve: resolve!, reject: reject! };
  };
}

@WORMSS
Copy link

WORMSS commented Mar 13, 2025

@Stadly you can put the ! on the declaration, rather than on the return object.

if (typeof Promise.withResolvers === "undefined") {
  Promise.withResolvers = <T>() => {
    let resolve!: (value: T | PromiseLike<T>) => void;
    let reject!: (reason?: unknown) => void;
    const promise = new Promise<T>((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

@Stadly
Copy link

Stadly commented Mar 14, 2025

Thanks, @WORMSS! I was not aware :)

@reymons
Copy link

reymons commented Apr 13, 2025

@lmammino this is not working:

Object.assign(Promise, {
	withResolvers: () => {
		let resolve;
		let reject;

		const promise = new Promise((resolve, reject) => {
			resolve = resolve;
			reject = reject;
		});

		return {
			promise,
			resolve,
			reject
		};
}});

const a = Promise.withResolvers()
a.promise.then(() => console.log('resolved'))
a.resolve();

With this error:

VM717:20 Uncaught TypeError: a.resolve is not a function
    at <anonymous>:20:3

But I see that it is because of the name that I am reassigning the variable inside the function, not the one outside. It's totally my fault. Sorry about that.

bruh 🙈

@pgadmin7
Copy link

For anyone that wants a ts version.

looked at bun https://bun.com/reference/globals/PromiseConstructor/withResolvers
something that stood out was that in bun the resolve prop is of type (value?: T | PromiseLike<T>) => void;

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Promise.withResolvers === undefined) {
    function __withResolvers<T, E = unknown>(): PromiseWithResolvers<T, E> {
      let resolve: PromiseWithResolvers<T,E>["resolve"];
      let reject: PromiseWithResolvers<T, E>["reject"];
      const promise = new Promise<T>((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve: resolve!, reject: reject! };
    }
    
    Object.defineProperty(Promise, "withResolvers", {
      value: __withResolvers,
      writable: false,
      configurable: false,
      enumerable: false
    });
}

declare global {
  export type PromiseWithResolvers<T, E = unknown> = {
    promise: Promise<T>;
    resolve: (value: T | PromiseLike<T>) => void;
    reject: (reason?: E) => void;
  };

  interface PromiseConstructor {
    /**
     * Create a deferred promise, with exposed resolve and reject methods
     * which can be called separately.
     * This is useful when you want to return a Promise
     * and have code outside the Promise resolve or reject it.
     */
    withResolvers<T>(): PromiseWithResolvers<T>;
    /**
     * Creates a new Promise and returns it in an object, along with its resolve and reject functions.
     *
     * @returns {PromiseWithResolvers<T>} An object with the properties promise, resolve, and reject.
     */
    withResolvers<T, E = unknown>(): {
      promise: Promise<T>;
      resolve: (value: T | PromiseLike<T>) => void;
      reject: (reason?: E) => void;
    };
  }
}

export {};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment