Tap into promise chains for debugging

Plus easy promise chaining using Ramda.

Powerful async data flow is only possible if combining the individual actions into a chain of promises is simple and hides the complexity, rather than increases it. Let me show how combining promises can be simple if we separate building individual steps from linking them into a directed graph.

Let us take a couple of async functions to play with

1
2
3
4
5
6
7
var q = require('q');
function add(a, b) {
return q(a + b);
}
function double(x) {
return q(x * 2);
}

Let us add two numbers, multiply that sum and then verify the result. We will place wrong value into the verify function on purpose.

1
2
3
4
5
6
7
8
9
10
add(2, 3)
.then(function multiply(result) {
return double(result);
})
.then(function verify(result) {
console.assert(result === 11);
})
.done();
// exception thrown because we are comparing against
// a wrong value

Notice that function multiply just passes the result argument to the double function. We can shorten our code a lot by removing the insignificant variable. The promise engine will just call double directly instead of calling multiply.

1
2
3
4
5
6
add(2, 3)
.then(double)
.then(function (result) {
console.assert(result === 11);
})
.done();

This is an example of point-free programming and I consider it a very useful technique to remove syntax noise from my code.

Tap into promise chain using Lodash

We still need to see why our assertion throws an error. Let us print the value passed between the add and double steps. We can use lodash/tap method to pass a value to an interceptor function, and then return it. The _.tap method takes value first, and the interceptor function second. This is why we need to partially apply console.log from the right to leave the first argument free.

1
2
3
4
5
6
7
8
9
var _ = require('lodash');
add(2, 3)
.then(_.partialRight(_.tap, console.log)) // 1
.then(double)
.then(function (result) {
console.assert(result === 11); // 2
})
.done();
// prints 5

We found the problem: the sum is 5, that means its doubled value should be 10. We can fix the error, but notice that we are mixing two concerns in the above code. We are linking actions into a chain using .then method. We also have complex logic that creates two steps right in place (lines // 1 and // 2). It is hard to tell what each action does unless you look into the code, which might be hard. The code is terse, and due to its point-free nature there are no function names to give us hints. I recommend forming actions separately and use good variable names for each step.

1
2
3
4
5
6
7
8
9
10
var _ = require('lodash');
var print = _.partialRight(_.tap, console.log);
var verify = function verify(result) {
console.assert(result === 10);
};
add(2, 3)
.then(print)
.then(double)
.then(verify)
.done();

This is much simpler code to understand. Can we do better? Yes, we remove a little more code. Let us rewrite verify function via equality and compose functions.

1
2
3
4
5
6
7
var print = _.partialRight(_.tap, console.log);
var verify = _.compose(console.assert, _.partial(_.isEqual, 11));
add(2, 3)
.then(print)
.then(double)
.then(verify)
.done();

Using Ramda to shorten code

We can shorten the code by switching from lodash to Ramda. There are several good reasons, but in this example, the currying by default + different argument order is the important part.

1
2
3
4
5
6
7
8
var R = require('ramda');
var print = R.tap(console.log);
var verify = R.compose(console.assert, R.eq(11));
add(2, 3)
.then(print)
.then(double)
.then(verify)
.done();

Recently, Ramda has added methods to compose promise returning functions (pCompose and pPipe). In our example, they can be used to shorten the actual chaining and remove triple .then call.

1
2
3
4
5
6
7
var R = require('ramda');
var print = R.tap(console.log);
var verify = R.compose(console.assert, R.eq(11));
var by2 = R.pPipe(print, double, verify);
add(2, 3)
.then(by2)
.done();

I still build individual actions separately from chaining the promise steps.

Multiple promise tracks

R.pPipe is a great tool, because it allows us to quickly create separate tracks of actions to take. For example, let us start by adding two numbers, and then multiply by 2 and verify the result. At the same time let us multiply the sum by 3. When both forks finish, let us print the 2 results.

add(2, 3)
    \___ multiply by 2 -> verify equals 10 --\
     \___ multiply by 3 ----------------------\
                                               \--> print [value1, value2]

We can use Ramda's built-in addition and multiplication functions to replace our add and mul functions

1
2
3
4
5
6
7
8
9
10
11
12
13
var R = require('ramda');
var print = R.tap(console.log);
var verify = R.compose(console.assert, R.eq(10));
var by2 = R.pPipe(R.multiply(2), R.tap(verify)); // chain 1
var by3 = R.pPipe(R.multiply(3)); // chain 2
var splitMultiply = function splitMultiply(x) {
return q.all([by2(x), by3(x)])
};
q(R.add(2, 3))
.then(splitMultiply)
.then(print)
.done();
// prints [10, 15]

Notice that we use R.tap(verify) in order to return the input value after verifying. We can probably simplify splitMultiply further, but even now it is very short and clear.

Update 1

Promise library Q has introduced new method API to tap into the promise chain Q.tap. You can now directly tap into a chain without writing a method yourself

1
2
3
Q('something')
.tap(console.log) // prints 'something'
.done(/* value = 'something' */);