Reputation: 43
I'm currently trying to figure out if it is possible to do something like this:
async function(x) {
...
}
try {
await Promise.all([function(1), function(2), function(3), ...]);
} catch (err) {
// Count number of successful Promises resolved at the point when one is rejected
return statistics(num_success);
}
Is this possible? The reason I am trying to do this in the catch block is so that I can terminate the function immediately if any errors.
If it is possible to do so, how can I achieve this?
Upvotes: 0
Views: 2772
Reputation: 60
as Raphael PICCOLO commened, you can use Promise.allSettled<T = any>(...promises: Promise<T>[])
. It returns a promise of array containing objects with status
property, assigned with "fulfilled" or "rejected", according to each promise fulfillness or rejectness. A value
property will be available if promise has fulfilled and, in case it rejects, a reason
property will be available instead.
Example code:
//...
const promises = []; //Array with promises.
let count = 0;
Promise.allSettled(promises)
.then(settled => {
settled.forEach(({ status, value, reason }) => {
if (status === 'fulfilled')
count++;
})
})
.then(() => statistics(count));
//...
Upvotes: 1
Reputation: 95614
If you'd like all of your promises to run until they're settled (fulfilled or rejected), you can do so with Promise.allSettled as in Raphael PICCOLO's comment and SrHenry's answer.
However, to preserve Promise.all's behavior of reacting to a rejection immediately, you'll need some additional custom behavior. This sounds like a good reason to wrap Promse.all.
/**
* Receives an array, like Promise.all, and runs until the first rejection.
*
* Unlike Promise.all, but like Promise.allSettled, this will always resolve
* to an object; however, like Promise.all, this will resolve as soon as it
* encounters the first rejection.
*
* Returns a promise that resolves to an object with these properties:
* success: boolean, whether Promise.all would have succeeded
* count: number, number of resolved Promises at the time of return
* results: Array, result array containing all resolved Promises/values
* error: any, the reject value that caused this to fail, or null
*/
function allWithProgress(arrayOfPromises) {
const results = new Array(arrayOfPromises);
let count = 0;
/**
* Given an input to Promise.resolve, increments results+count
* when complete.
*/
function wrap(valueOrThenable, index) {
return Promise.resolve(valueOrThenable).then(x => {
results[index] = x;
count++;
return x;
});
}
// slice(0) prevents the results array from being modified.
// You could also add a condition check that prevents `wrap` from
// modifying the results after it returns.
return Promise
.all(arrayOfPromises.map(wrap)) // or "(e, i) => wrap(e, i)"
.then(x => ({success: true, count, results: results.slice(0), error: null}))
.catch(e => ({success: false, count, results: results.slice(0), error: e}));
}
// Test harness below
function timeoutPromise(string, timeoutMs) {
console.log("Promise created: " + string + " - " + timeoutMs + "ms");
return new Promise(function(resolve, reject) {
window.setTimeout(function() {
console.log("Promise resolved: " + string + " - " + timeoutMs + "ms");
resolve(string);
}, timeoutMs);
});
}
Promise.resolve().then(() => {
// success
return allWithProgress([
timeoutPromise("s1", 1000),
timeoutPromise("s2", 2000),
timeoutPromise("s3", 3000),
"not a promise"
]).then(console.log);
}).then(() => {
// failure
return allWithProgress([
timeoutPromise("f1", 1000),
timeoutPromise("f2", 2000)
// rejects with a String for Stack Snippets; use an Error in real code
.then(() => Promise.reject("f2 failed")),
timeoutPromise("f3", 3000),
"not a promise"
]).then(console.log);
});
Note that ES6 Promises can't be canceled or rolled back in any standard way, so in the test case f3
is not prevented from completing. If it is important to stop those promises in flight, you'll need to write that logic yourself.
Upvotes: 1