Andrew.

Promise composition

In the last article on promises, we focused on the Promises/A+ specification and the ES6 implementation of promises. We learned the benefits of using promises over raw callbacks. Now, we’re going to take it up a notch and focus on promise composition, which we’ll define as functional programming and asynchronous control flow using promises.

More about chaining

The then method allows us to chain promises (see 3.2.6 in Promises/A+ spec). The value returned from a chain of promises is itself a promise. This returned promise will be resolved to the value returned from the last onFulfilled or onRejected handlers in the chain. Let’s look at some examples:

Won’t this blow the stack? Unfortunately, JavaScript does not have proper tail call support yet. However, it won’t affect this recursive call because Promises/A+ requires the onFulfilled and onRejected handlers to be called on a future turn in the event loop after the stack unwinds (3.2.4 in Promises/A+).

Starting chains and grouping promises

In addition to the functional programming concepts we enjoy in synchronous programming, ES6 promises provide tools to aid in composition. We’ll focus on two of them: Promise.resolve and Promise.all.

Promise.resolve(value) helps us start promise chains. If no value is provided, a promise is returned fulfilled to undefined. We’ll call this an “empty” promise. If a value is provided, a promise is returned fulfilled to that value:

Promise.resolve('monkeys').then(console.log) // will log 'monkeys'

The second tool is Promise.all. It takes an array of promises and returns a new promise, which we’ll call a “group” promise. The group promise will either be resolved when all the promises have been fulfilled or rejected when any have been rejected.

Promise.all is helpful for grouping the fulfillment values from multiple promises, regardless if the execution is done in series or parallel:

Promise.all maintains the ordering of the results array, so the result of doThis() would be index 0 and so on. If either doThis() or doThat() had an error, groupPromise would be rejected and we’d log it with console.error.

Working with collections

Let’s look at iterating through collections of data that require asynchronous action per element. First, let’s mimic async.map using promises:

Here, statCache returns a value or a promise. Regardless of what’s returned, we can group it and provide the results with Promise.all. Sweet!

How can this work? The Promise.all method also takes in values as arguments which internally it transforms into a promises fulfilled to those values.

However, there is a problem with our map function. What if an exception is thrown in statCache? Right now, the exception wouldn’t be caught since it isn’t in a promise chain. Here is where Promise.resolve comes in:

In the above example, we used Promise.all to group operations done in series and Promise.resolve to start our promise chain. Each time through arr.map, we built a larger chain and returned a promise for that point in the chain until we reached the end of the array. If we unraveled this code, it would look something like this: