Passing multiple arguments in promises

How to pass multiple values from one promise step to the next one without lexical scope or global variables.

Promises are really simple to use when passing a single argument from the first operation to the next one. What if we need to pass multiple arguments? For example, imagine we need to generate two numbers and then sum them up, all in an asynchronous manner. We can write these simple functions to start:

1
2
3
4
5
6
7
8
9
10
var Q = require('q');
function getA() {
return Q(4);
}
function getB() {
return Q(2);
}
function sum(a, b) {
return Q(a + b);
}

If we need to perform each operation in sequence, we get back to the original pyramid of doom of nested callbacks.

1
2
3
4
5
6
7
8
9
getA()
.then(function (a) {
return getB().then(function (b) {
return sum(a, b);
});
})
.tap(console.log)
.done();
// 6

The pyramid is necessary to keep a and b in the lexical scope for the last step (calling sum(a, b)).

We can simplify the pyramid a little if we are allowed to run getA and getB in parallel

1
2
3
4
5
6
7
Q.all([getA(), getB()])
.then(function (ab) {
return sum(ab[0], ab[1]);
})
.tap(console.log)
.done();
// 6

We are using Q.all method to execute multiple promises in parallel and get a single promise back that resolves with an array. The ab array passed to the next step has the result of each individual promise in the same order as the promises passed to Q.all([...]).

Taking it one step further, there is Q.spread that can be used after Q.all to separate results into individual arguments.

1
2
3
4
5
6
7
Q.all([getA(), getB()])
.spread(function (a, b) {
return sum(a, b);
})
.tap(console.log)
.done();
// 6

Think of Q.all().then(fn) as executing fn.call(null, [results]), while Q.all().spread(fn) as executing fn.apply(null, results).

We can even skip the intermediate function and call the sum directly using point-free style

1
2
3
4
5
Q.all([getA(), getB()])
.spread(sum)
.tap(console.log)
.done();
// 6

Using the original pure function, sum is very convenient, because we can still apply the partial application to prefill some arguments. For example to always add 2 to the number returned from getA we can use either the initial step or the partial application

using initial step
1
2
3
4
5
Q.all([getA(), 10])
.spread(sum)
.tap(console.log)
.done();
// 14
using partial application
1
2
3
4
5
Q.all([getA()])
.spread(sum.bind(null, 10))
.tap(console.log)
.done();
// 14

Accumulating variables

Let us go back to the original sequence where we executed getA and getB in sequence. We can use Q.all and Q.spread to pass extra arguments to the next step without using an object or an array.

1
2
3
4
5
6
7
8
getA()
.then(function (a) {
return Q.all([a, getB()]);
})
.spread(sum)
.tap(console.log)
.done();
// 6

All we have done here was to push the promise returned by getB to the array returned by Q.all, which then we spread into sum arguments. Compare this solution to the original pyramid-like solution at the beginning of this post. We are not relying on the lexical scope any more, and we keep the promise chain very flat. Imagine we want to print both operands and the result instead of printing the result only.

1
2
3
4
5
6
7
8
9
10
11
12
getA()
.then(function (a) {
return Q.all([a, getB()]);
})
.spread(function (a, b) {
return Q.all([a, b, sum(a, b)]);
})
.spread(function (a, b, result) {
console.log('adding', a, 'to', b, 'makes', result);
})
.done();
// adding 4 to 2 makes 6

Update - ES6

In ES6 we can combine Promise.all method with parameter destructuring to place the results into separate named variables in a single command

1
2
3
4
Promise.all([p1, p2, p3])
.then(([r1, r2, r3]) => {
// use results r1, r2, r3
})