DEV Community

Cover image for Functional JavaScript: Resolving Promises Sequentially
JavaScript Joel
JavaScript Joel

Posted on • Edited on

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))
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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’s then function.

For simplicity sake, this example just resolve index 0 of 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)
    })
   })
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

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([]))
Enter fullscreen mode Exit fullscreen mode

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([]))
Enter fullscreen mode Exit fullscreen mode

That’s it! A pretty simple... scratch that... short function that will resolve Promises sequentially.
Lastly, let’s slap it all together.

/*
 * promiseSerial resolves Promises sequentially.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * const funcs = urls.map(url => () => $.ajax(url))
 *
 * promiseSerial(funcs)
 *   .then(console.log)
 *   .catch(console.error)
 */
const promiseSerial = funcs =>
  funcs.reduce((promise, func) =>
    promise.then(result => func().then(Array.prototype.concat.bind(result))),
    Promise.resolve([]))

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

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

// execute Promises in serial
promiseSerial(funcs)
  .then(console.log.bind(console))
  .catch(console.error.bind(console))
Enter fullscreen mode Exit fullscreen mode

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 here and Twitter (@joelnet). Or if you think I’m full of shit, tell me in the comments below.

Cheers!

More Articles @ https://medium.com/@joelthoms/latest

Originally posted @ https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e

Top comments (0)