Remove boilerplate from promise chains

How to remove boilerplate from intermediate steps using partial application or currying.

I prefer to start my promise chains using a dummy value in order to catch any possible errors, see Starting promises. Let us see two delays one after another

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var Q = require('q');
function delay(N) {
var defer = Q.defer();
setTimeout(defer.resolve, n);
return defer.promise;
}
Q()
.then(delay.bind(null, 1000))
.then(delay.bind(null, 2000))
.then(function () {
console.log('all done');
})
.done();
// prints "all done" after 3 seconds

When we shift more functions from the start of the promise chain to middle steps, we are forced to create temporary functions using partial application, compare

1
2
3
4
5
6
delay(1000)
.then(...)
// with
Q()
.then(delay.bind(null, 1000))
.then(...)

I think the partial application using an external function, like R.partial is a little more clearer, because Function.prototype.bind has a dual purpose making it confusing.

1
2
3
4
var R = require('ramda');
Q()
.then(R.partial(delay, 1000))
.then(...)

Taking it one (some might say unnecessary) step further, we can remove even more boilerplate. Instead of partially applying the function at the call time, we can create a curried function in the first place.

1
2
3
4
5
6
7
8
9
var R = require('ramda');
var delay = R.curryN(2, function _delay(N) {
var defer = Q.defer();
setTimeout(defer.resolve, n);
return defer.promise;
});
Q()
.then(delay(1000))
.then(...)

Note, that I am using R.curryN to curry an unary _delay as if it were a binary function. This is necessary because we want to NOT execute the function as soon as a single argument is provided. Instead we want to execute the _delay function when it is called again (with the previous resolved value that will be ignored).

1
2
3
4
5
var delay = R.curryN(2, function _delay(N) { ... });
var temp = delay(100) // returns a new function
temp() // executes _delay(100), returns a promise
// same as
delay(100)() // executes _delay(100), returns a promise

Just remember that if you need to use the curried version at the start of the promise chain you have to call it again

1
2
delay(100)()
.then(...)