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.
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.
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.
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.
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.
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:
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.
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:
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}`);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With