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.

Illustration of Autocomplete by Martin Bonov

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.

Middleware architecture on a slide by Jon Crosby

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): latestResult

const 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. S_ervices_ 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):

Remarks

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.

Robin Pokorny on YouTube_JavaScript is my passion: I like to write JavaScript, I like to read JavaScript, and I like to talk JavaScript._www.youtube.com/c/robinpokorny

Related articles