November 11, 2014
Promise.any() – A Missing Use Case
$q library in AngularJS 1.0. It is much more bare bones than modern promise implementations, but for me it was leaps and bounds better than the callback hell I’ve found myself stuck in. Since AngularJS 1.0, the promise specification has become more mature and native browser implementations have been popping up (though you’ll still need a polyfill). So, if you haven’t learned how to use promises, now might be the time!
One of my “breakthrough” moments in learning about promises was when I read “You’re Missing the Point of Promises” by Domenic Denicola. A big concept I grasped after reading Domenic’s article is that promises make asynchronous return values relevant again. At the risk of being yet another basic explanation of promises, here is the gist of what that means for a developer.
When dealing with a standard asynchronous method, such as the
$.get() method from jQuery (you shouldn’t trust jQuery “promises”), the return value is useless:
There are plenty of other reasons why promises are better than callbacks. However, I want to talk about a recent problem I had to solve around aggregating multiple promises.
I was trying to implement a search form on a site for one of our clients. When the user enters a search query I make several AJAX requests to different APIs. If any of these requests come back with successful results, I want to navigate to a page with the results. If one fails, that’s OK, as long as at least one succeeds. If they all fail I want to stay on the same page and display an error message.
Promise.all() implements a boolean AND operation on whether each promise rejects or resolves. For this problem, a boolean OR operation is needed. Since there is no existing implementation like this, I decided to write my own.
I implemented a method I’m calling
Promise.any(). It takes an array of promises and resolves if at least one of the promises resolves. The full implementation is at https://gist.github.com/jkjustjoshing/de488c63074370e28169#file-promise-any-js — a slimmed-down version is shown below:
There are two main things happening here:
1. Each promise, whether it resolves or rejects, gets mapped to an internally used promise that resolves to an object with two values:
- resolve – whether the initial promise was resolved or rejected
- result – the resolve or reject value of the initial promise
Promise.all(), the internally used promises are inspected – if at least one is resolved, the returning promise is resolved. If none of the promises are resolved, the returning promise is rejected.
I can then solve my problem with the following code:
A few things to note about this solution:
1. If the
Promise.any() promise resolves, any individual promises that rejected will appear to be
null in the result array. This means that any promise that resolves to
null will break this implementation. For my use case this isn’t an issue, but it is a large enough flaw that you won’t see my implementation included in any promise libraries.
2. If the
Promise.any() promise rejects, it will reject with an array of all rejection objects. This is different from
Promise.all(), which rejects to only the first promise that rejects.
Are there any better ways to do this? Let me know in the comments.
UPDATE – A friend of mine has pointed out that the Q promise library has a
Q.allSettled() method, which is a similar implementation to my
Promise.any() implementation. The Q implementation doesn’t suffer from the issues mine does and works in a more general situation. One more example why the author, Kris Kowal, is very well known for his work with promises!