Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between Promise.any() and Promise.race()

What is the difference between Promise.any() and Promise.race(), and how do they get used differently?


From MDN,

Also, unlike Promise.race(), which returns the first settled value, this method returns the first resolved value. This method will ignore all rejected promises up until the first promise that resolves.

So that brings me to, the difference between resolved and settled. Which then brought me to the MDN promises page, which then brought me to States and Fates

Being settled is not a state, just a linguistic convenience.

So we have Promise.any and Promise.race for linguistic convenience? i.e. there is no difference. Another instance of this equality, is "A promise whose fate is unresolved is necessarily pending." and "We say that a promise is settled if it is not pending, i.e. if it is either fulfilled or rejected.".

So if a promise is resolved, it is not unresolved, so it is not pending. So then, if it's not pending, it's settled. So resolved === settled.

like image 763
Ben Butterworth Avatar asked May 11 '20 14:05

Ben Butterworth


People also ask

What is Promise any?

Promise. any() takes an iterable of Promise objects. It returns a single promise that fulfills as soon as any of the promises in the iterable fulfills, with the value of the fulfilled promise.

What is the difference between Promise all and Promise allSettled?

all() method returns an array as an output containing promise data inside several indexes. Promise. allSettled() method returns an array of objects and each of these objects further contains two properties further status and value.

What is the use of Promise Race ()?

race() The Promise. race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.

What are the 3 states of a Javascript Promise?

A Promise is in one of these states: pending: initial state, neither fulfilled nor rejected. fulfilled: meaning that the operation was completed successfully. rejected: meaning that the operation failed.


Video Answer


1 Answers

Promise.race and Promise.any do different things:

Promise.race is settled as soon as any of the promises you feed it settle, whether they are fulfilled or rejected.

Promise.any is settled as soon as any of the promises you feed it is fulfilled or they are all rejected, in which case it's rejected with an AggregateError.

The chief differences are:

  1. race's promise is rejected when the first promise you give it is rejected; any's promise isn't, because another promise may be fulfilled instead.

  2. any's promise's rejection reason will be an AggregateError, but race's rejection reason will be the rejection reason from the first promise that was rejected.

So if you pass them both an array of two promises, and one of the promises is rejected, then afterward the other promise is fulfilled, the promise from Promise.race will be rejected (because the first promise to settle was rejected) and the promise from Promise.any will be fulfilled (because although the first promise was rejected, the second was fulfilled). E.g.:

const a = new Promise((_, reject) => setTimeout(reject,  100, new Error("a")));
const b = new Promise((resolve)   => setTimeout(resolve, 200, "b"));

Promise.race([a, b]).then(
    value => {
        console.log(`race: fulfilled with ${value}`);
    },
    reason => {
        console.log(`race: rejected with ${reason.message}`);
    }
);

Promise.any([a, b]).then(
    value => {
        console.log(`any:  fulfilled with ${value}`);
    },
    reason => {
        console.log(`any:  rejected with ${reason.errors.map(({message}) => message).join()}`);
    }
);

With a JavaScript engine that has Promise.any (or a polyfill), that outputs

race: rejected with a
any:  fulfilled with b

Play with various outcomes here (there's a very rough incomplete stand-in for Promise.any included if your browser doesn't have it yet):

addFakeAnyIfMissing();

document.querySelector("input[value='Start Again']").addEventListener("click", run);

run();

function setupPromise(name) {
    return new Promise((resolve, reject) => {
        const div = document.querySelector(`[data-for="${name}"]`);
        const btnFulfill = div.querySelector("input[value=Fulfill]");
        const btnReject  = div.querySelector("input[value=Reject]");;
        const display    = div.querySelector(".display");
        btnFulfill.disabled = btnReject.disabled = false;
        display.textContent = "pending";
        btnFulfill.onclick = () => {
            resolve(name);
            display.textContent = `fulfilled with ${name}`;
            btnFulfill.disabled = btnReject.disabled = true;
        };
        btnReject.onclick = () => {
            reject(new Error(name));
            display.textContent = `rejected with Error(${name})`;
            btnFulfill.disabled = btnReject.disabled = true;
        };
    });
}

function run() {
    const a = setupPromise("a");
    const b = setupPromise("b");
    const raceDisplay = document.querySelector("[data-for=race] .display");
    const anyDisplay  = document.querySelector("[data-for=any]  .display");
    raceDisplay.textContent = anyDisplay.textContent = "pending";

    Promise.race([a, b]).then(
        value => {
            raceDisplay.textContent = `fulfilled with ${value}`;
        },
        reason => {
            raceDisplay.textContent = `rejected with ${reason.message}`;
        }
    );

    Promise.any([a, b]).then(
        value => {
            anyDisplay.textContent = `fulfilled with ${value}`;
        },
        reason => {
            anyDisplay.textContent = `rejected with ${reason.errors.map(({message}) => message).join()}`;
        }
    );
}

function addFakeAnyIfMissing() {
    if (!Promise.any) {
        // VERY ROUGH STANDIN, not a valid polyfill
        class AggregateError extends Error {}
        Object.defineProperty(Promise, "any", {
            value(iterable) {
                return new Promise((resolve, reject) => {
                    const errors = [];
                    let waitingFor = 0;
                    for (const value of iterable) {
                        const index = waitingFor++;
                        Promise.resolve(value).then(
                            value => {
                                resolve(value);
                                --waitingFor;
                            },
                            reason => {
                                errors[index] = reason;
                                if (--waitingFor === 0) {
                                    reject(Object.assign(new AggregateError(), {errors}));
                                }
                            }
                        );
                    }
                });
            },
            writable: true,
            configurable: true
        });
    }
}
<div data-for="a">
    Promise A
    <input type="button" value="Fulfill">
    <input type="button" value="Reject">
    <span class="display"></span>
</div>
<div data-for="b">
    Promise B
    <input type="button" value="Fulfill">
    <input type="button" value="Reject">
    <span class="display"></span>
</div>
<div data-for="race">
    <code>Promise.race([a, b])</code>:
    <span class="display"></span>
</div>
<div data-for="any">
    <code>Promise.any([a, b])</code>:
    <span class="display"></span>
</div>
<input type="button" value="Start Again">

This chart from the proposal may help:

There are four main combinators in the Promise landscape.

+−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−+
| name               | description                                     |                 |
+−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−+
| Promise.allSettled | does not short-circuit                          | Added in ES2020 |
| Promise.all        | short-circuits when an input value is rejected  | Added in ES2015 |
| Promise.race       | short-circuits when an input value is settled   | Added in ES2015 |
| Promise.any        | short-circuits when an input value is fulfilled | this proposal   |
+−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−+

Continuing with your question...

Another instance of this equality, is "A promise whose fate is unresolved is necessarily pending." and "We say that a promise is settled if it is not pending, i.e. if it is either fulfilled or rejected.".

So if a promise is resolved, it is not unresolved, so it is not pending. So then, if its not pending, its settled. So resolved === settled.

I can see how you got there, but you can't invert it like that. :-) A resolved promise can be pending. It's just that an unresolved promise is definitely pending.

The states are:

  • pending
  • fulfilled
  • rejected

You can resolve a promise (A) to another promise (B), which means that while A may still be pending, nothing can change what's going to happen to it; its fate is sealed, it will be fulfilled or rejected according to what happens to B.

(More about this in my blog post Let's talk about how to talk about promises.)

Here's an example of a pending resolved promise:

const b = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (Math.random() < 0.5) {
            resolve("all good");
        } else {
            reject(new Error("ugh"));
        }
    }, 100);
});

// (Being verbose for clarity)
const a = new Promise((resolve, reject) => {
    resolve(b);
    // Now, `a` is pending, but resolved
    // No matter what else we do, `a`'s fate is tied to
    // `b`'s. For instance, this does nothing:
    resolve("foo");
    // Neither does this:
    reject(new Error("foo"));
});

b
.then(value => {
    console.log(`b was fulfilled: ${value}`);
})
.catch(reason => {
    console.log(`b was rejected: ${reason.message}`);
});

a
.then(value => {
    console.log(`a was fulfilled: ${value}`);
})
.catch(reason => {
    console.log(`a was rejected: ${reason.message}`);
});
like image 59
T.J. Crowder Avatar answered Oct 23 '22 12:10

T.J. Crowder