/dev/urandom

/dev/urandom

Pseudorandom thoughts generator.

I'm Niccolò Maggioni.
Student, geek and developer.

Avoiding Promise.all() fail-fast behavior

JavaScript’s 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.all is rejected if one of the elements is rejected and Promise.all fails fast: If you have four promises which resolve after a timeout, and one rejects immediately, then Promise.all rejects immediately.

For example, as the MDN explains:

Fail-fast demonstrationMDN Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "Promise 1");
});
let p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, "Promise 2");
});
let p3 = new Promise((resolve, reject) => {
reject("Promise 3");
});
Promise.all([p1, p2, p3]).then(value => {
console.log(value);
}, reason => {
console.log(reason)
});

This will only output…

1
Promise 3

…since the Promise returned by Promise.all() is immediately rejected because of the inner p3. The other p1 & 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:

Dirty hack? Dirty hack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "Everything OK in Promise 1");
});
let p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, "Everything OK in Promise 2");
});
let p3 = new Promise((resolve, reject) => {
resolve(new Error("Something went wrong in Promise 3!"));
});
Promise.all([p1, p2, p3]).then((values) => {
for (let i = 0; i < values.length; ++i) {
if (values[i] instanceof Error) {
console.log("ERR: " + values[i].message);
} else {
console.log(values[i]);
}
}
});

That will output:

1
2
3
Everything OK in Promise 1
Everything OK in Promise 2
ERR: Something went wrong in Promise 3!

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.

r-cyr's solutionReddit comment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "Everything OK in Promise 1");
});
let p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, "Everything OK in Promise 2");
});
let p3 = new Promise((resolve, reject) => {
reject(new Error("Something went wrong in Promise 3!"));
});
const toResultObject = (promise) => {
return promise
.then(result => ({ success: true, result }))
.catch(error => ({ success: false, error }));
};
Promise.all([p1, p2, p3].map(toResultObject)).then((values) => {
for (let i = 0; i < values.length; ++i) {
if (!values[i].success) {
console.log("ERR: " + values[i].error);
} else {
console.log(values[i].result);
}
}
});

Share this