Functional JavaScript: Resolving Promises Sequentially

I love the new Promise library that ships with ES6, though one thing has been left out, a function to sequentially execute multiple promises.

We can use a library like Q, Bluebird, RSVP.js, Async, etc., or we can make our own. I really only need a single function and it seemed kind of heavy to import a whole library for one function which is why I created this.

One trait of the Promise is that it executes immediately. This actually works against us, we’ll need the Promises to execute when we are ready for it to execute.

The way I did this was to convert each Promise into a factory function. The factory function will be a simple function that will return a Promise. Now our Promises will execute when we decide.

For this *cough* contrived example, I’ve decided to use jQuery’s ajax method as my promise.

// some dummy urls to resolve
const urls = ['/url1', '/url2', '/url3']

// convert each url to a function that returns an ajax call
const funcs = urls.map(url => () => $.ajax(url))

Solving this problem is a little complex, and I find it helps me to think ahead a little bit about what our function should output. Probably something like this:

Promise.resolve()
.then(x => funcs[0]()) // resolve func[0]
.then(x => funcs[1]()) // resolve func[1]
.then(x => funcs[2]()) // resolve func[2]

I also want the final promise to return an array that contains the results of each promise.

This was the most complex part. I needed to start each promise with an empty array [] and then concatenate the results of each promise to that array. Stick with me, I’ll try my best to break it down.

I’m gonna start off this Promise with an initial value of an empty array like this Promise.resolve([]). Then execute each factory function using the Promise’sthen function.

For simplicity sake, this example just resolve index 0of func. We’ll do the rest later.

// start our promise off with an empty array. this becomes all.
Promise.resolve([])
// all is the array we will append each result to.
.then(all => {
return funcs[0]().then(result => {
// concat the resolved promise result to all
return all.concat(result)
})
})

This block of code can be expressed this in a more compact way by removing all the {, }, and return from our code.

Promise.resolve([])
.then(all => funcs[0]().then(result => all.concat(result)))

A neat trick to get rid of that arrow function is directly call concat like this:

Promise.resolve([])
.then(all => funcs[0]().then(Array.prototype.concat.bind(all)))

And finally, this will be the output of our function:

Promise.resolve([])
.then(x => funcs[0]().then(Array.prototype.concat.bind(x)))
.then(x => funcs[1]().then(Array.prototype.concat.bind(x)))
.then(x => funcs[2]().then(Array.prototype.concat.bind(x)))

That wasn’t so bad was it? Now that we know our input and output, let’s make!

We could use a for loop (but that’s not very functional), we could also use recursion, but what I really like for this problem is reduce.

Whenever you need to transform a list into a single object, consider using reduce.

Our promiseSerial function should take an array of factory functions (that each return a Promise) and reduce them into the single Promise chain expressed above.

Our initial value of Promise.resolve([]) is passed into our reduce method like this:

const promiseSerial = funcs =>
funcs.reduce((promise, func) => ???, Promise.resolve([]))

The last piece is to generalize one of our Promise then's from above and update some argument names. (the new part is in bold)

const promiseSerial = funcs =>
funcs.reduce((promise, func) =>
promise.then(result =>
func().then(Array.prototype.concat.bind(result))),
Promise.resolve([]))

That’s it! A pretty simple… scratch that… short function that will resolve Promises sequentially.

Lastly, let’s slap it all together.

Now we have eliminated the need to install a 3rd party library with our shiny new promiseSerial function.

Hey! You actually made it to the end of this article!

What is your use case for resolving promises sequentially? How did you solve this? Please share your experiences.

I know it’s a small thing, but it makes my day when I get those follow notifications on Medium and Twitter (@joelnet). Or if you think I’m full of shit, tell me in the comments below.

Cheers!

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!
Like what you read? Give Joel Thoms a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.