Es6 Promise.All() Error Handle - Is .Settle() Needed

ES6 Promise.all() error handle - Is .settle() needed?

Am I going about this the wrong way or is extending the ES6 Promises
with a settle method the right thing to do here?

You can't directly use Promise.all() to generate .settle() type behavior that gets you all the results whether any reject or not because Promise.all() is "fast-fail" and returns as soon as the first promise rejects and it only returns that reject reason, none of the other results.

So, something different is needed. Often times, the simplest way to solve that problem is by just adding a .then() handler to whatever operation creates your array of promises such that it catches any rejects and turns them into fulfilled promises with some specific value that you can test for. But, that type of solution is implementation dependent as it depends upon exactly what type of value you are returning so that isn't entirely generic.

If you want a generic solution, then something like .settle() is quite useful.

You can't use the structure:

Promise.all([...]).settle(...).then(...);

Note (added in 2019): It appears the Promise standards effort has picked Promise.allSettled() as a standard implementation of "settle-like" behavior. You can see more on that at the end of this answer.

Because Promise.all() rejects when the first promise you pass it rejects and it returns only that rejection. The .settle() logic works like:

Promise.settle([...]).then(...);

And, if you're interested, here's a fairly simple implementation of Promise.settle():

// ES6 version of settle
Promise.settle = function(promises) {
function PromiseInspection(fulfilled, val) {
return {
isFulfilled: function() {
return fulfilled;
}, isRejected: function() {
return !fulfilled;
}, isPending: function() {
// PromiseInspection objects created here are never pending
return false;
}, value: function() {
if (!fulfilled) {
throw new Error("Can't call .value() on a promise that is not fulfilled");
}
return val;
}, reason: function() {
if (fulfilled) {
throw new Error("Can't call .reason() on a promise that is fulfilled");
}
return val;
}
};
}

return Promise.all(promises.map(function(p) {
// make sure any values are wrapped in a promise
return Promise.resolve(p).then(function(val) {
return new PromiseInspection(true, val);
}, function(err) {
return new PromiseInspection(false, err);
});
}));
}

In this implementation, Promise.settle() will always resolve (never reject) and it resolves with an array of PromiseInspection objects which allows you to test each individual result to see whether it resolved or rejected and what was the value or reason for each. It works by attaching a .then() handler to each promise passed in that handles either the resolve or reject from that promise and puts the result into a PromiseInspection object which then becomes the resolved value of the promise.

You would then use this implementation like this;

Promise.settle([...]).then(function(results) {
results.forEach(function(pi, index) {
if (pi.isFulfilled()) {
console.log("p[" + index + "] is fulfilled with value = ", pi.value());
} else {
console.log("p[" + index + "] is rejected with reasons = ", pi.reason());
}
});
});

FYI, I've written another version of .settle myself that I call .settleVal() and I often find it easier to use when you don't need the actual reject reason, you just want to know if a given array slot was rejected or not. In this version, you pass in a default value that should be substituted for any rejected promise. Then, you just get a flat array of values returned and any that are set to the default value where rejected. For example, you can often pick a rejectVal of null or 0 or "" or {} and it makes the results easier to deal with. Here's the function:

// settle all promises.  For rejected promises, return a specific rejectVal that is
// distinguishable from your successful return values (often null or 0 or "" or {})
Promise.settleVal = function(rejectVal, promises) {
return Promise.all(promises.map(function(p) {
// make sure any values or foreign promises are wrapped in a promise
return Promise.resolve(p).then(null, function(err) {
// instead of rejection, just return the rejectVal (often null or 0 or "" or {})
return rejectVal;
});
}));
};

And, then you use it like this:

Promise.settleVal(null, [...]).then(function(results) {
results.forEach(function(pi, index) {
if (pi !== null) {
console.log("p[" + index + "] is fulfilled with value = ", pi);
}
});
});

This isn't an entire replacement for .settle() because sometimes you may want to know the actual reason it was rejected or you can't easily distinguish a rejected value from a non-rejected value. But, I find that more than 90% of the time, this is simpler to use.


Here's my latest simplification for .settle() that leaves an instanceof Error in the return array as the means of distinguishing between resolved values and rejected errors:

// settle all promises.  For rejected promises, leave an Error object in the returned array
Promise.settleVal = function(promises) {
return Promise.all(promises.map(function(p) {
// make sure any values or foreign promises are wrapped in a promise
return Promise.resolve(p).catch(function(err) {
let returnVal = err;
// instead of rejection, leave the Error object in the array as the resolved value
// make sure the err is wrapped in an Error object if not already an Error object
if (!(err instanceof Error)) {
returnVal = new Error();
returnVal.data = err;
}
return returnVal;
});
}));
};

And, then you use it like this:

Promise.settleVal(null, [...]).then(function(results) {
results.forEach(function(item, index) {
if (item instanceof Error) {
console.log("p[" + index + "] rejected with error = ", item);
} else {
console.log("p[" + index + "] fulfilled with value = ", item);
}
});
});

This can be a complete replacement for .settle() for all cases as long as an instanceof Error is never a resolved value of your promises (which it really shouldn't be).


Promise Standards Effort

As of 2019, it appears that .allSettled() is becoming the standard for this type of behavior. And, here's a polyfill:

if (!Promise.allSettled) {
Promise.allSettled = function(promises) {
let wrappedPromises = Array.from(promises).map(p =>
this.resolve(p).then(
val => ({ state: 'fulfilled', value: val }),
err => ({ state: 'rejected', reason: err })
)
);
return this.all(wrappedPromises);
}
}

Usage would be like this:

let promises = [...];    // some array of promises, some of which may reject
Promise.allSettled(promises).then(results => {
for (let r of results) {
if (r.state === 'fulfilled') {
console.log('fulfilled:', r.val);
} else {
console.log('rejected:', r.err);
}
}
});

Note that Promise.allSettled() itself always resolves, never rejects though subsequent .then() handlers could throw or return a rejected promise to make the whole chain reject.

As of June 2019, this not yet in the current desktop Chrome browser, but is planned for an upcoming release (e.g. later in 2019).

Handling errors in Promise.all

Promise.all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.

Some libraries have something called Promise.when, which I understand would instead wait for all promises in the array to either resolve or reject, but I'm not familiar with it, and it's not in ES6.

Your code

I agree with others here that your fix should work. It should resolve with an array that may contain a mix of successful values and errors objects. It's unusual to pass error objects in the success-path but assuming your code is expecting them, I see no problem with it.

The only reason I can think of why it would "not resolve" is that it's failing in code you're not showing us and the reason you're not seeing any error message about this is because this promise chain is not terminated with a final catch (as far as what you're showing us anyway).

I've taken the liberty of factoring out the "existing chain" from your example and terminating the chain with a catch. This may not be right for you, but for people reading this, it's important to always either return or terminate chains, or potential errors, even coding errors, will get hidden (which is what I suspect happened here):

Promise.all(state.routes.map(function(route) {
return route.handler.promiseHandler().catch(function(err) {
return err;
});
}))
.then(function(arrayOfValuesOrErrors) {
// handling of my array containing values and/or errors.
})
.catch(function(err) {
console.log(err.message); // some coding error in handling happened
});

Wait until all promises complete even if some rejected

Benjamin's answer offers a great abstraction for solving this issue, but I was hoping for a less abstracted solution. The explicit way to to resolve this issue is to simply call .catch on the internal promises, and return the error from their callback.

let a = new Promise((res, rej) => res('Resolved!')),
b = new Promise((res, rej) => rej('Rejected!')),
c = a.catch(e => { console.log('"a" failed.'); return e; }),
d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
.then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
.catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
.then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
.catch(err => console.log('Catch', err));

Taking this one step further, you could write a generic catch handler that looks like this:

const catchHandler = error => ({ payload: error, resolved: false });

then you can do

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
.then(results => console.log(results))
.catch(() => console.log('Promise.all failed'))
< [ 'Resolved!', { payload: Promise, resolved: false } ]

The problem with this is that the caught values will have a different interface than the non-caught values, so to clean this up you might do something like:

const successHandler = result => ({ payload: result, resolved: true });

So now you can do this:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
.then(results => console.log(results.filter(result => result.resolved))
.catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

Then to keep it DRY, you get to Benjamin's answer:

const reflect = promise => promise
.then(successHandler)
.catch(catchHander)

where it now looks like

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
.then(results => console.log(results.filter(result => result.resolved))
.catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

The benefits of the second solution are that its abstracted and DRY. The downside is you have more code, and you have to remember to reflect all your promises to make things consistent.

I would characterize my solution as explicit and KISS, but indeed less robust. The interface doesn't guarantee that you know exactly whether the promise succeeded or failed.

For example you might have this:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

This won't get caught by a.catch, so

> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]

There's no way to tell which one was fatal and which was wasn't. If that's important then you're going to want to enforce and interface that tracks whether it was successful or not (which reflect does).

If you just want to handle errors gracefully, then you can just treat errors as undefined values:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

In my case, I don't need to know the error or how it failed--I just care whether I have the value or not. I'll let the function that generates the promise worry about logging the specific error.

const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});

That way, the rest of the application can ignore its error if it wants, and treat it as an undefined value if it wants.

I want my high level functions to fail safely and not worry about the details on why its dependencies failed, and I also prefer KISS to DRY when I have to make that tradeoff--which is ultimately why I opted to not use reflect.

Is it possible to catch all rejected promises in Promise.all?

Of course, doing this will require waiting until all the input promises have settled. As long as one is pending, which might reject, you can't be sure you have all the rejections!

So you can use Promise.all, but after transforming the input promises so as to catch rejections and identify them, perhaps by wrapping them in an Error object, as in

caughtPromises = promises.map(promise => promise.catch(Error));

Now filter the results of Promise.all to find the ones identified as failing:

Promise.all(caughtPromises)
.then(results => results.filter(result => result instanceof Error))

et voilà.

Why does JavaScript's `Promise.all` not run all promises in failure conditions?

The asynchronous operations associated with the promises are all run. If one of those promises rejects, then Promise.all() simply does not wait for all of them to complete, it rejects when the first promise rejects. That is just how it was designed to work. If you need different logic (like you want to wait for all of them to be done, no matter whether they fulfill or reject), then you can't use just Promise.all().

Remember, a promise is not the async operation itself. A promise is just an object that keeps track of the state of the async operation. So, when you pass an array of promises to Promise.all(), all those async operations have already been started and are all in-flight already. They won't be stopped or cancelled.

Why does Promise.all discard promises if any of them reject, since I would expect it to wait for "all" promises to settle.

It works the way it does because that's how it was designed and that is a very common use case when you don't want your code to continue if there was any sort of error. If it happens to not be your use case, then you need to use some implementation of .settle() which has the behavior you want (which you seem to already know).

What I find the more interesting question is why is there not a .settle() option in the specification and standard implementation since it is also a fairly common use case. Fortunately, as you have found, it is not a lot of code to make your own. When I don't need the actual reject reason and just want some indicator value to be placed into the array, I often use this fairly simple to use version:

// settle all promises.  For rejeted promises, return a specific rejectVal that is
// distinguishable from your successful return values (often null or 0 or "" or {})
Promise.settleVal = function(rejectVal, promises) {
return Promise.all(promises.map(function(p) {
// make sure any values or foreign promises are wrapped in a promise
return Promise.resolve(p).catch(function(err) {
// instead of rejection, just return the rejectVal (often null or 0 or "" or {})
return rejectVal;
});
}));
};

// sample usage:
Promise.settleVal(null, someArrayOfPromises).then(function(results) {
results.forEach(function(r) {
// log successful ones
if (r !== null) {
console.log(r);
}
});
});

what exactly does "discard" mean?

It just means that the promises are no longer tracked by Promise.all(). The async operations they are associated with keep right on doing whatever they were going to do. And, in fact if those promises have .then() handlers on them, they will be called just as they normally would. discard does seem like an unfortunate term to use here. Nothing happens other than Promise.all() stops paying attention to them.


FYI, if I want a more robust version of .settle() that keeps track of all results and reject reasons, then I use this:

// ES6 version of settle that returns an instanceof Error for promises that rejected
Promise.settle = function(promises) {
return Promise.all(promises.map(function(p) {
// make sure any values or foreign promises are wrapped in a promise
return Promise.resolve(p).catch(function(err) {
// make sure error is wrapped in Error object so we can reliably detect which promises rejected
if (err instanceof Error) {
return err;
} else {
var errObject = new Error();
errObject.rejectErr = err;
return errObject;
}
});
}));
}

// usage
Promise.settle(someArrayOfPromises).then(function(results) {
results.forEach(function(r) {
if (r instanceof Error) {
console.log("reject reason", r.rejectErr);
} else {
// fulfilled value
console.log("fulfilled value:", r);
}
});
});

This resolves to an array of results. If a result is instanceof Error, then it was a rejected, otherwise it's a fulfilled value.

Filter/recover Promise.all result

If you're using Q, then there's a function called Q.allSettled that basically does what you ask for.

Otherwise, this simple function will give you the results of all promises, and tell you whether the succeeded or failed. You can then do whatever you need to do with the promises that succeeded or failed.

/** * When every promise is resolved or rejected, resolve to an array of * objects *   { result: [ Promise result ], success: true / false }**/function allSettled(promises) {  return Promise.all(    promises.map(      promise => promise.then(        // resolved        (result) => ({ result: result, success: true }),        // rejected        (result) => ({ result: result, success: false })      )    )  );}
// example usage:const one = Promise.resolve(1);const two = Promise.reject(2);const three = Promise.resolve(3);
allSettled([ one, two, three ]) .then((results) => { console.log(results[0]); // { result: 1, success: true } console.log(results[1]); // { result: 2, success: false } console.log(results[2]); // { result: 3, success: true } });

Handling an array of promises from Promise.all

One promise resolves with one result. Promise.all() resolves to an array of results, one result for each promise that was in the original array of promises in the same order as the promises.

how would I then handle this array of promises in my server.js?

aHandlerObject.extract(data).then(results => {
// results is an array of resolved values
// one for each call to this._getReleaseId(...)
}).catch(err => {
// handle error here
});

When to use promise.all()?

I'm not sure anyone has really given the most general purpose explanation for when to use Promise.all() (and when not to use it):

What is(are) the correct scenario(s) to use promise.all()

Promise.all() is useful anytime you have more than one promise and your code wants to know when all the operations that those promises represent have finished successfully. It does not matter what the individual async operations are. If they are async, are represented by promises and your code wants to know when they have all completed successfully, then Promise.all() is built to do exactly that.

For example, suppose you need to gather information from three separate remote API calls and when you have the results from all three API calls, you then need to run some further code using all three results. That situation would be perfect for Promise.all(). You could so something like this:

Promise.all([apiRequest(...), apiRequest(...), apiRequest(...)]).then(function(results) {
// API results in the results array here
// processing can continue using the results of all three API requests
}, function(err) {
// an error occurred, process the error here
});

Promise.all() is probably most commonly used with similar types of requests (as in the above example), but there is no reason that it needs to be. If you had a different case where you needed to make a remote API request, read a local file and read a local temperature probe and then when you had data from all three async operations, you wanted to then do some processing with the data from all three, you would again use Promise.all():

Promise.all([apiRequest(...), fs.promises.readFile(...), readTemperature(...)]).then(function(results) {
// all results in the results array here
// processing can continue using the results of all three async operations
}, function(err) {
// an error occurred, process the error here
});

On the flip side, if you don't need to coordinate among them and can just handle each async operation individually, then you don't need Promise.all(). You can just fire each of your separate async operations with their own .then() handlers and no coordination between them is needed.

In addition Promise.all() has what is called a "fast fail" implementation. It returns a master promise that will reject as soon as the first promise you passed it rejects or it will resolve when all the promises have resolved. So, to use Promise.all() that type of implementation needs to work for your situation. There are other situations where you want to run multiple async operations and you need all the results, even if some of them failed. Promise.all() will not do that for you directly. Instead, you would likely use something like Promise.settle() for that situation. You can see an implementation of .settle() here which gives you access to all the results, even if some failed. This is particularly useful when you expect that some operations might fail and you have a useful task to pursue with the results from whatever operations succeeded or you want to examine the failure reasons for all the operations that failed to make decisions based on that.

Are there any best practices to use promise.all()? Should it be
ideally used only if all of the promise objects are of the same or
similar types?

As explained above, it does not matter what the individual async operations are or if they are the same type. It only matters whether your code needs to coordinate them and know when they all succeed.


It's also useful to list some situations when you would not use Promise.all():

  1. When you only have one async operation. With only one operation, you can just use a .then() handler on the one promise and there is no reason for Promise.all().
  2. When you don't need to coordinate among multiple async operations.
  3. When a fast fail implementation is not appropriate. If you need all results, even if some fail, then Promise.all() will not do that by itself. You will probably want something like Promise.allSettled() instead.
  4. If your async operations do not all return promises, Promise.all() cannot track an async operation that is not managed through a promise.


Related Topics



Leave a reply



Submit