First result in a sequence of promises
Recently we were implementing a feature in our app that resulted in a nice kata-like task, encouraging me to play with JavaScript Promises and deepening my knowledge of them.

The task:
For an autocomplete there are several services to ask for suggestions on user input. Those services are ordered from our own servers (cheap), to partners', to Google's (expensive). We want to show the user the first non-empty response. Moreover, we want to ask the next service only if the previous service suggested no items.
Oh, and one part that resembled the kata, too, was that I had a lot of fun solving it.
Problem in a kata form
In technical terms a service is an asynchronous function which returns a promise. This promise, when resolved, contains an array of suggestions. The array can be empty. Please note that an empty result is different to rejecting the promise.
Given a list of these services invoke each service, wait for its result, and then either return the result if non-empty or continue with the next service.
Now it's the time to try it yourself—if you want to—before I show my solution. Start with this boilerplate with basic test cases:
Existing libraries and solutions
A small obstacle in my research for existing solutions was that I did not know how to describe it in keywords. In the end I tried ‘first’, ‘sequence’, ‘cascade’, ‘waterfall’, and ‘fallback’ combined with the obvious ‘promise’. Then I had to filter out numerous results describing Promise.all
or Promise.race
.
promise-fallback
After some research I found this NPM package which basically solves the problem. It uses a (rather cumbersome) recursion and overall is neither easily understandable nor elegant. See the code on GitHub (in CoffeeScript). However, mutatis mutandis it could be used for our needs.
Executing Promises in Sequence
The second approach for a similar task (first existing file in a file names list) is explained in a more recent article by Cory LaViska. It also takes advantage of recursion. Although it feels tidier (no CoffeeScript helps 😜), I cannot say it reads well. The author himself concludes with a question ‘Do you have a more elegant approach?’
My solution
Still, I hoped for an approach that would be small, readable, and maintainable. My ideal solution would be analogous to koa middlewares.
Iteration 1: Promise.reduce, Koa, and wrappers
In the end, Cory's article has proven to be extremely useful as it pointed me to the Bluebird documentation where I discovered Promise.reduce.

While Promise.reduce
is meant for different usages—e.g. summing content of multiple files—it performs one important thing. When going through the array ‘the result of the promise is awaited, before continuing with next iteration.’
Instead of recursively calling the fallback function, I wanted to create a promise chain. So I wrapped each service in a wrapper function that receives the latest result so far. Then if this result is empty it calls the service and returns the promise it receives. In the other way it simply passes on the result—as if the path does not match in a koa middleware.
It looks like this:
const myServiceWrapper = (latestResult) =>
!latestResult.length
? myService(userInput)
: latestResultconst partnerServiceWrapper = …
const googleServiceWrapper = …
Such wrappers can easily be chained:
Promise.resolve([])
.then(myServiceWrapper)
.then(partnerServiceWrapper)
.then(googleServiceWrapper)
Here Promise.resolve([])
serves as a starter of the promise chain. Thanks to it we can immediately use then
. Also, it sets the latestResult
for the first wrapper to []
.
Eureka! This nearly solves our problem!
Iteration 2: Reduce to five lines
These wrappers are simple, readable, and independent. Which hence means maintainable.
So what is left to come? Firstly, we do not want to write the boilerplate. Secondly, the number of services is unknown. We want to pass our function just a given array of services.
Coincidently, we solve them both in one step. An important hint here is the name of aforementioned Promise.reduce
.
The five (!) line solution does exactly the same as the wrappers chain above for any number of services using Array.reduce
(Ooooh! 😃).
We start the chain again with Promise.resolve([])
, then in each iteration prev
is a promise and next
is a service.
const firstResult = (services, userInput) => services.reduce(
(prev, next) => prev.then((result) =>
!result.length ? next(userInput) : result
),
Promise.resolve([])
)
Generalised solution
Later I generalised the code for any ‘first non-empty result in a sequence of promises’ use case. Services are now ambiguous tasks.
Do you like Flow? See the solution with type annotations below!
As the second argument you can pass some options (with default values):
args=[]
: Arguments to be passed to each taskinitial=undefined
: A value to start the promise chain withisEmpty=(x) => !x
: A function to determine if the result is empty. Should be fast as it is run repeatedly.
Remarks
- Because the code is de facto five lines I decided not to publish it as an standalone NPM package. Or should I?
- As mentioned, number of times the
isEmpty
function is called is always the same as the number of tasks. This is a drawback of isolation. - In the example,
!result.length
is not a good real-world condition as it would throw an exception if theresult
was undefined. - When any of the tasks is rejected the whole encapsulating promise is also rejected.
Bonus: Solution with Flow type annotations
For clarity I present the solution above with Flow type annotations, which make the whole code a bit longer; although, the main part is still about five lines.
Note that the generic type T
corresponds to the results of the tasks and, hence, to the overall result.
Notation ?T
means that the type is nullable, see explanation.
Please help: Do you find the Flow annotations helpful? Does it add value for you? Should I keep adding them?
If you like this post, please don’t forget to give a 👏 below. Every clap notification is a motivational boost for me.
If you would like to learn more, I recently started a YouTube channel about JavaScript. I post new video every week, so consider subscribing. Be there from the beginning and help me get better.
Related articles
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!
