Let's say I have a set of Promise
s that are making network requests, of which one will fail:
// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]
Promise.all(arr)
.then(res => console.log('success', res))
.catch(err => console.log('error', err)) // This is executed
Let's say I want to wait until all of these have finished, regardless of if one has failed. There might be a network error for a resource that I can live without, but which if I can get, I want before I proceed. I want to handle network failures gracefully.
Since Promises.all
doesn't leave any room for this, what is the recommended pattern for handling this, without using a promises library?
This question is related to
javascript
promise
es6-promise
I had the same problem and have solved it in the following way:
const fetch = (url) => {
return node-fetch(url)
.then(result => result.json())
.catch((e) => {
return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout));
});
};
tasks = [fetch(url1), fetch(url2) ....];
Promise.all(tasks).then(......)
In that case Promise.all
will wait for every Promise will come into resolved
or rejected
state.
And having this solution we are "stopping catch
execution" in a non-blocking way. In fact, we're not stopping anything, we just returning back the Promise
in a pending state which returns another Promise
when it's resolved after the timeout.
Similar answer, but more idiomatic for ES6 perhaps:
const a = Promise.resolve(1);_x000D_
const b = Promise.reject(new Error(2));_x000D_
const c = Promise.resolve(3);_x000D_
_x000D_
Promise.all([a, b, c].map(p => p.catch(e => e)))_x000D_
.then(results => console.log(results)) // 1,Error: 2,3_x000D_
.catch(e => console.log(e));_x000D_
_x000D_
_x000D_
const console = { log: msg => div.innerHTML += msg + "<br>"};
_x000D_
<div id="div"></div>
_x000D_
Depending on the type(s) of values returned, errors can often be distinguished easily enough (e.g. use undefined
for "don't care", typeof
for plain non-object values, result.message
, result.toString().startsWith("Error:")
etc.)
I just wanted a polyfill that exactly replicated ES2020 behaviour since I'm locked into node versions a lot earlier than 12.9 (when Promise.allSettled
appeared), unfortunately. So for what it's worth, this is my version:
const settle = (promise) => (promise instanceof Promise) ?
promise.then(val => ({ value: val, status: "fulfilled" }),
err => ({ reason: err, status: "rejected" })) :
{ value: promise, status: 'fulfilled' };
const allSettled = async (parr) => Promise.all(parr.map(settle));
This handles a mixed array of promise and non-promise values, as does the ES version. It hands back the same array of { status, value/reason }
objects as the native version.
There is a finished proposal for a function which can accomplish this natively, in vanilla Javascript: Promise.allSettled
, which has made it to stage 4, is officialized in ES2020, and is implemented in all modern environments. It is very similar to the reflect
function in this other answer. Here's an example, from the proposal page. Before, you would have had to do:
function reflect(promise) {
return promise.then(
(v) => {
return { status: 'fulfilled', value: v };
},
(error) => {
return { status: 'rejected', reason: error };
}
);
}
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');
Using Promise.allSettled
instead, the above will be equivalent to:
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');
Those using modern environments will be able to use this method without any libraries. In those, the following snippet should run without problems:
Promise.allSettled([_x000D_
Promise.resolve('a'),_x000D_
Promise.reject('b')_x000D_
])_x000D_
.then(console.log);
_x000D_
Output:
[
{
"status": "fulfilled",
"value": "a"
},
{
"status": "rejected",
"reason": "b"
}
]
For older browsers, there is a spec-compliant polyfill here.
Here's my custom settledPromiseAll()
const settledPromiseAll = function(promisesArray) {
var savedError;
const saveFirstError = function(error) {
if (!savedError) savedError = error;
};
const handleErrors = function(value) {
return Promise.resolve(value).catch(saveFirstError);
};
const allSettled = Promise.all(promisesArray.map(handleErrors));
return allSettled.then(function(resolvedPromises) {
if (savedError) throw savedError;
return resolvedPromises;
});
};
Compared to Promise.all
If all promises are resolved, it performs exactly as the standard one.
If one of more promises are rejected, it returns the first one rejected much the same as the standard one but unlike it waits for all promises to resolve/reject.
For the brave we could change Promise.all()
:
(function() {
var stdAll = Promise.all;
Promise.all = function(values, wait) {
if(!wait)
return stdAll.call(Promise, values);
return settledPromiseAll(values);
}
})();
CAREFUL. In general we never change built-ins, as it might break other unrelated JS libraries or clash with future changes to JS standards.
My settledPromiseall
is backward compatible with Promise.all
and extends its functionality.
People who are developing standards -- why not include this to a new Promise standard?
Promise.all
with using modern async/await
approach
const promise1 = //...
const promise2 = //...
const data = await Promise.all([promise1, promise2])
const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]
This should be consistent with how Q does it:
if(!Promise.allSettled) {
Promise.allSettled = function (promises) {
return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
state: 'fulfilled',
value: v,
}), r => ({
state: 'rejected',
reason: r,
}))));
};
}
var err;
Promise.all([
promiseOne().catch(function(error) { err = error;}),
promiseTwo().catch(function(error) { err = error;})
]).then(function() {
if (err) {
throw err;
}
});
The Promise.all
will swallow any rejected promise and store the error in a variable, so it will return when all of the promises have resolved. Then you can re-throw the error out, or do whatever. In this way, I guess you would get out the last rejection instead of the first one.
I don't know which promise library you are using, but most have something like allSettled.
Edit: Ok since you want to use plain ES6 without external libraries, there is no such method.
In other words: You have to loop over your promises manually and resolve a new combined promise as soon as all promises are settled.
Update, you probably want to use the built-in native Promise.allSettled
:
Promise.allSettled([promise]).then(([result]) => {
//reach here regardless
// {status: "fulfilled", value: 33}
});
As a fun fact, this answer below was prior art in adding that method to the language :]
Sure, you just need a reflect
:
const reflect = p => p.then(v => ({v, status: "fulfilled" }),
e => ({e, status: "rejected" }));
reflect(promise).then((v => {
console.log(v.status);
});
Or with ES5:
function reflect(promise){
return promise.then(function(v){ return {v:v, status: "fulfilled" }},
function(e){ return {e:e, status: "rejected" }});
}
reflect(promise).then(function(v){
console.log(v.status);
});
Or in your example:
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]
Promise.all(arr.map(reflect)).then(function(results){
var success = results.filter(x => x.status === "fulfilled");
});
I've been using following codes since ES5.
Promise.wait = function(promiseQueue){
if( !Array.isArray(promiseQueue) ){
return Promise.reject('Given parameter is not an array!');
}
if( promiseQueue.length === 0 ){
return Promise.resolve([]);
}
return new Promise((resolve, reject) =>{
let _pQueue=[], _rQueue=[], _readyCount=false;
promiseQueue.forEach((_promise, idx) =>{
// Create a status info object
_rQueue.push({rejected:false, seq:idx, result:null});
_pQueue.push(Promise.resolve(_promise));
});
_pQueue.forEach((_promise, idx)=>{
let item = _rQueue[idx];
_promise.then(
(result)=>{
item.resolved = true;
item.result = result;
},
(error)=>{
item.resolved = false;
item.result = error;
}
).then(()=>{
_readyCount++;
if ( _rQueue.length === _readyCount ) {
let result = true;
_rQueue.forEach((item)=>{result=result&&item.resolved;});
(result?resolve:reject)(_rQueue);
}
});
});
});
};
The usage signature is just like Promise.all
. The major difference is that Promise.wait
will wait for all the promises to finish their jobs.
Benjamin Gruenbaum answer is of course great,. But I can also see were Nathan Hagen point of view with the level of abstraction seem vague. Having short object properties like e & v
don't help either, but of course that could be changed.
In Javascript there is standard Error object, called Error
,. Ideally you always throw an instance / descendant of this. The advantage is that you can do instanceof Error
, and you know something is an error.
So using this idea, here is my take on the problem.
Basically catch the error, if the error is not of type Error, wrap the error inside an Error object. The resulting array will have either resolved values, or Error objects you can check on.
The instanceof inside the catch, is in case you use some external library that maybe did reject("error")
, instead of reject(new Error("error"))
.
Of course you could have promises were you resolve an error, but in that case it would most likely make sense to treat as an error anyway, like the last example shows.
Another advantage of doing it this, array destructing is kept simple.
const [value1, value2] = PromiseAllCatch(promises);
if (!(value1 instanceof Error)) console.log(value1);
Instead of
const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect..
if (!error1) { console.log(value1); }
You could argue that the !error1
check is simpler than an instanceof, but your also having to destruct both v & e
.
function PromiseAllCatch(promises) {_x000D_
return Promise.all(promises.map(async m => {_x000D_
try {_x000D_
return await m;_x000D_
} catch(e) {_x000D_
if (e instanceof Error) return e;_x000D_
return new Error(e);_x000D_
}_x000D_
}));_x000D_
}_x000D_
_x000D_
_x000D_
async function test() {_x000D_
const ret = await PromiseAllCatch([_x000D_
(async () => "this is fine")(),_x000D_
(async () => {throw new Error("oops")})(),_x000D_
(async () => "this is ok")(),_x000D_
(async () => {throw "Still an error";})(),_x000D_
(async () => new Error("resolved Error"))(),_x000D_
]);_x000D_
console.log(ret);_x000D_
console.log(ret.map(r =>_x000D_
r instanceof Error ? "error" : "ok"_x000D_
).join(" : ")); _x000D_
}_x000D_
_x000D_
test();
_x000D_
You can execute your logic sequentially via synchronous executor nsynjs. It will pause on each promise, wait for resolution/rejection, and either assign resolve's result to data
property, or throw an exception (for handling that you will need try/catch block). Here is an example:
function synchronousCode() {_x000D_
function myFetch(url) {_x000D_
try {_x000D_
return window.fetch(url).data;_x000D_
}_x000D_
catch (e) {_x000D_
return {status: 'failed:'+e};_x000D_
};_x000D_
};_x000D_
var arr=[_x000D_
myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"),_x000D_
myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js"),_x000D_
myFetch("https://ajax.NONEXISTANT123.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js")_x000D_
];_x000D_
_x000D_
console.log('array is ready:',arr[0].status,arr[1].status,arr[2].status);_x000D_
};_x000D_
_x000D_
nsynjs.run(synchronousCode,{},function(){_x000D_
console.log('done');_x000D_
});
_x000D_
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
_x000D_
I would do:
var err = [fetch('index.html').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }),
fetch('http://does-not-exist').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })];
Promise.all(err)
.then(function (res) { console.log('success', res) })
.catch(function (err) { console.log('error', err) }) //never executed
I think the following offers a slightly different approach... compare fn_fast_fail()
with fn_slow_fail()
... though the latter doesn't fail as such... you can check if one or both of a
and b
is an instance of Error
and throw
that Error
if you want it to reach the catch
block (e.g. if (b instanceof Error) { throw b; }
) . See the jsfiddle.
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p1_delayed_resolvement'), 2000);
});
var p2 = new Promise((resolve, reject) => {
reject(new Error('p2_immediate_rejection'));
});
var fn_fast_fail = async function () {
try {
var [a, b] = await Promise.all([p1, p2]);
console.log(a); // "p1_delayed_resolvement"
console.log(b); // "Error: p2_immediate_rejection"
} catch (err) {
console.log('ERROR:', err);
}
}
var fn_slow_fail = async function () {
try {
var [a, b] = await Promise.all([
p1.catch(error => { return error }),
p2.catch(error => { return error })
]);
console.log(a); // "p1_delayed_resolvement"
console.log(b); // "Error: p2_immediate_rejection"
} catch (err) {
// we don't reach here unless you throw the error from the `try` block
console.log('ERROR:', err);
}
}
fn_fast_fail(); // fails immediately
fn_slow_fail(); // waits for delayed promise to resolve
I know that this question has a lot of answers, and I'm sure must (if not all) are correct. However it was very hard for me to understand the logic/flow of these answers.
So I looked at the Original Implementation on Promise.all()
, and I tried to imitate that logic - with the exception of not stopping the execution if one Promise failed.
public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]>
{
let promise: Promise<{ data: any, isSuccess: boolean }[]>;
if (promisesList.length)
{
const result: { data: any, isSuccess: boolean }[] = [];
let count: number = 0;
promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) =>
{
promisesList.forEach((currentPromise: Promise<any>, index: number) =>
{
currentPromise.then(
(data) => // Success
{
result[index] = { data, isSuccess: true };
if (promisesList.length <= ++count) { resolve(result); }
},
(data) => // Error
{
result[index] = { data, isSuccess: false };
if (promisesList.length <= ++count) { resolve(result); }
});
});
});
}
else
{
promise = Promise.resolve([]);
}
return promise;
}
Explanation:
- Loop over the input promisesList
and execute each Promise.
- No matter if the Promise resolved or rejected: save the Promise's result in a result
array according to the index
. Save also the resolve/reject status (isSuccess
).
- Once all Promises completed, return one Promise with the result of all others.
Example of use:
const p1 = Promise.resolve("OK");
const p2 = Promise.reject(new Error(":-("));
const p3 = Promise.resolve(1000);
promiseExecuteAll([p1, p2, p3]).then((data) => {
data.forEach(value => console.log(`${ value.isSuccess ? 'Resolve' : 'Reject' } >> ${ value.data }`));
});
/* Output:
Resolve >> OK
Reject >> :-(
Resolve >> 1000
*/
Instead of rejecting, resolve it with a object. You could do something like this when you are implementing promise
const promise = arg => {_x000D_
return new Promise((resolve, reject) => {_x000D_
setTimeout(() => {_x000D_
try{_x000D_
if(arg != 2)_x000D_
return resolve({success: true, data: arg});_x000D_
else_x000D_
throw new Error(arg)_x000D_
}catch(e){_x000D_
return resolve({success: false, error: e, data: arg})_x000D_
}_x000D_
}, 1000);_x000D_
})_x000D_
}_x000D_
_x000D_
Promise.all([1,2,3,4,5].map(e => promise(e))).then(d => console.log(d))
_x000D_
I really like Benjamin's answer, and how he basically turns all promises into always-resolving-but-sometimes-with-error-as-a-result ones. :)
Here's my attempt at your request just in case you were looking for alternatives. This method simply treats errors as valid results, and is coded similar to Promise.all
otherwise:
Promise.settle = function(promises) {
var results = [];
var done = promises.length;
return new Promise(function(resolve) {
function tryResolve(i, v) {
results[i] = v;
done = done - 1;
if (done == 0)
resolve(results);
}
for (var i=0; i<promises.length; i++)
promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
if (done == 0)
resolve(results);
});
}
Source: Stackoverflow.com