Skip to content

Instantly share code, notes, and snippets.

@lmammino
Created July 18, 2023 17:57
Show Gist options
  • Save lmammino/ef121da874a80d657379a1cd64bf8166 to your computer and use it in GitHub Desktop.
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

The problem of this solution is that calling resolve can not exist yet!

@aralroca
Copy link

aralroca commented Sep 3, 2024

This is better:

if (!Promise.withResolvers) {
    Promise.withResolvers = function() {
        let resolve, reject;

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

        // wait a bit
        while (resolve === undefined || reject === undefined) {}

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

@lmammino
Copy link
Author

lmammino commented Sep 3, 2024

Hello @aralroca,
Thanks for submitting this, but be aware that the executor function (the function you pass to the Promise constructor) is executed synchronously, as soon as the promise is constructed, so there's no way for resolve or reject to be undefined. (source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise#:~:text=The%20executor%20is%20called%20synchronously%20(as%20soon%20as%20the%20Promise%20is%20constructed)%20with%20the%20resolveFunc%20and%20rejectFunc%20functions%20as%20arguments.)

For example this code should work as intended:

Promise.withResolvers = function () {
  let resolve, reject
  const promise = new Promise((res, rej) => {
    resolve = res
    reject = rej
  })
  return { promise, resolve, reject }
}

const { promise, resolve, reject } = Promise.withResolvers()
console.log(resolve, reject) // synchronously check the value of `resolve` and `reject`

promise.then(console.log)
resolve('Hello, world!')

It prints:

[Function (anonymous)] [Function (anonymous)]
Hello, world!

Moreover, I would strongly discourage using for or while loops (or other synchronous mechanisms) to wait for asynchronous operations to progress. You would just risk to block the event loop indefinitely...

I hope this makes sense.

@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! };
  };
}

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