Avoiding Promise.all() fail-fast behavior
Promise.all() is a great tool for when you need a bunch of Promises to all resolve before moving on in your world conquering strategy, but sometimes it can also throw a spanner in the works due to its fail-fast behavior.
As documented in the MDN article on the method:
Promise.allis rejected if one of the elements is rejected and
Promise.allfails fast: If you have four promises which resolve after a timeout, and one rejects immediately, then
For example, as the MDN explains:
This will only output…
…since the Promise returned by
Promise.all() is immediately rejected because of the inner
p3. The other
p2 Promises are then waited for, but their result is ignored.
I’ve stumbled across this behavior while I was writing a NodeJS interface for - shameless self-promotion ahead - Gerph, my networked configuration manager.
This caused me some trouble, since my initial approach was to have an array of Promises either resolve, if the key was found (HTTP status !== 204), or reject, if the key wasn’t found or other problems occurred.
After a while I realized that if a key lookup failed somewhere in the middle of the array, all the subsequent keys would have been ignored; better said: their URLs would have been called and their bodies parsed, but their results would have been skipped and not saved or processed.
Luckily enough a Twitter conversation by Thomas Parisot popped up while I was browsing for a solution, and a reply by Vincent Voyer saved my day: making all Promises resolve either way.
Now, I know that it might not sound like the best solution out there and I’m also pretty sure that there’s a package for that in the infinite NPM universe, but I didn’t like the idea of pulling no-one-knows-how-many dependencies into the project for such a simple task.
Jokes apart, having all Promises always resolve and check for errors in the returned value at a later stage kind of neglects the principles of Promises themselves, but hey, it works and it is a solid solution if implemented correctly, IMO.
An approach following this concept might be as follows:
That will output:
As you can see, now all the Promises are processed correctly and in order. The error can anyway be picked out by checking if one of the return values is an
instanceof Error, and it can be handled as you normally would; in this example it is just logged with
ERR: prepended to it for the sake of demoing the concept.
I know that this is an “high-risk” post as it exposes a quite patchy technique - it works, but I’m well aware that it is not the cleanest of the solutions. If you have any suggestion on how to improve or entirely replace this workaround, ping me on one of my social networks!
Reddit’s user r-cyr suggested another solution: mapping promise results to objects containing a
success boolean and the value/error itself.