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 resolveconst urls = ['/url1', '/url2', '/url3']// convert each url to a function that returns an ajax callconst 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 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 allreturn 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!
Rethinking JavaScript: Break is the GOTO of loops_In my last article, Death of the for Loop, I tried to convince you to abandon the for loop for a more functional…_medium.com
Rethinking JavaScript: Death of the For Loop_JavaScript’s for loop has served us well, but it is now obsolete and should be retired in favor of newer functional…_hackernoon.com
Rethinking JavaScript: The if statement_Thinking functionally has opened my mind about programming._medium.com