Shorten promises (advanced)

Advanced examples removing promise boilerplate code.

Yesterday I posted shortened promises that has raised questions regarding the benefits of compacting promise-based asynchronous code. A strong opinion (and I partially agree) was: such compact code becomes harder to debug and to make more robust. Consider the original code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var Q = require('q');
var glob = require('glob');
function grabMarkdownFiles(folder) {
var defer = Q.defer();
glob(folder + '/*.md', function (err, files) {
if (err) {
defer.reject(err);
} else {
defer.resolve(files);
}
});
return defer.promise;
}
grabMarkdownFiles('../..').then(console.log, console.error);

I can easily add a check // 1 inside the grabMarkdownFiles to make sure the passed folder argument is a valid string, plus output collected files // 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Q = require('q');
var glob = require('glob');
var verify = require('check-types').verify;
function grabMarkdownFiles(folder) {
verify.unemptyString(folder, 'expected folder name'); // 1
var defer = Q.defer();
glob(folder + '/*.md', function (err, files) {
console.log('found', files.length, 'inside', folder); // 2
if (err) {
defer.reject(err);
} else {
defer.resolve(files);
}
});
return defer.promise;
}
grabMarkdownFiles('../..').then(console.log, console.error);

I cannot add specific argument checks or logging statements to my shortened version

1
2
var glob = require('q').denodeify(require('glob'));
glob('../../*.md').then(console.log, console.error);

Whenever I have a trade off between shorter code and easier to understand and use code, I pick the later. But in this case, promise-based code can be made robust using the following tricks.

Faster debugging using full stack traces

You can enable full stack traces in Q promise library using global option at a slight performance penalty. You should also write custom print function that prints the exception's stack trace (console.error does not do this)

1
2
3
4
5
6
7
8
9
10
var Q = require('q');
Q.longStackSupport = true;
Q('foo')
.then(function () {
throw new Error('on purpose')
})
.fail(onError);
function onError(err) {
console.error(err.stack);
}
output
1
2
3
4
5
6
7
$ node promises.js
Error: on purpose
at promises.js:6:9
at node.js:901:3
From previous event:
at Object.<anonymous> (promises.js:6:2)
at node.js:901:3

Long stack traces are a great time savior, especially in longer chains.

Passing value along chain

You don't have to create a promise object just to return a value, as long as there is original promise to start the chain. The easiest way is to wrap the original value in Q(value) call, that returns a promise that resolves with the passed value. If any link function returns a non-promise value, it is similarly passed along to the next step:

1
2
3
4
5
6
var Q = require('q');
Q('foo')
.then(function (value) {
return value;
})
.then(console.log, console.error);

output

$ node promises.js
foo

Exit when chain completes

If you need to exit the Nodejs process, queue the exit call onto the chain, otherwise nothing might execute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Q = require('q');
Q('foo')
.then(function (value) {
console.log(value);
return value;
})
.then(function (value) {
var defer = Q.defer();
setTimeout(function () {
console.log('waited', value);
defer.resolve(value);
}, 1000);
return defer.promise;
})
.then(console.log, console.error);
process.exit(0);

output

$ node promises.js
// nothing!

In this example, the first two links of the chain are synchronous (returning 'foo', printing value). Still the chain starts execution only after the current code block finishes. It never finishes, since process.exit is the last command. For more ways promise-based chains can play tricks using event queue execution see my Concurrency can bite you even in Node blog post.

You can at least execute the first link of the chain by placing process.exit on the event queue itself

1
2
3
// same code as above
.then(console.log, console.error)
process.nextTick(process.exit.bind(null, 0));

output

$ node promises.js
foo

Same result could be achieved (less eficiently) using defer function. All it does is set 1ms timeout, then executes the exit call.

1
2
3
4
var _ = require('lodash');
// same code as above
.then(console.log, console.error);
_.defer(process.exit.bind(null, 0));

The best way is to tack the exit call onto the chain itself, and make sure it executes in all cases

1
2
3
// same code as above
.then(console.log, console.error)
.fin(process.exit.bind(null, 0));

output

$ node promises.js
foo
waited foo
foo

Tapping into chain to print value

I often need to inspect values going into or coming out of links of the promise chain. Using standard console.log does not work, since it does not return a value.

1
2
3
4
var Q = require('q');
Q('foo')
.then(console.log)
.then(console.log, console.error);
$ node promises.js
foo
undefined

One can write an utility function to log and return the value (or all arguments)

1
2
3
4
5
6
7
8
var Q = require('q');
function print(value) {
console.log(value);
return value;
}
Q('foo')
.then(print)
.then(console.log, console.error);

It can become tiresome to write these functions, so here is a shortcut

1
2
3
4
5
6
var Q = require('q');
var _ = require('lodash');
var print = _.partialRight(_.tap, console.log);
Q('foo')
.then(print)
.then(console.log, console.error);
$ node promises.js
foo
foo

I am using two functions from an excellent lodash library. tap function calls its second (right) argument with the first (left) argument, then returns the first (left) argument.

1
2
3
_.tap(value, interceptor)
// calls interceptor function with value
// returns value

We don't have the first argument yet, it will be whatever the previous promise resolves with. We only have the second argument console.log that we want to bind as the second (right) argument. That is what partialRight function does

1
_.partialRight(func, [arg])

Together function print does the following: accepts single argument, calls console.log on it, then returns the argument.

Argument checking

I love defensive and even paranoid coding. Taking shortcuts with promises makes checking arguments difficult. But one can still tap into the chain in order to do pre- or post- validation.

My favorite library for checking types and values in JavaScript is Phil Booth's check-types. For individual methods, we can apply same method we did to console.log

1
2
var verify = require('check-types').verify;
var verifyUnemptyString = _.partialRight(_.tap, verify.unemptyString);

I like to write additional message with my assertion to provide context and allow easier debugging, so my wrapping is a little more verbose:

1
2
3
4
5
6
7
var verify = require('check-types').verify;
function verifyNumber(message) {
return function (value) {
verify.number(value, message);
return value;
};
}

Both ways return the original value, so they can tap into the chain. Putting it all together, in combination with error handling tips above, I get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Q = require('q');
var verify = require('check-types').verify;
var _ = require('lodash');
var print = _.partialRight(_.tap, console.log);
// error handling
Q.longStackSupport = true;
function onError(err) {
console.error(err.stack);
}
function verifyNumber(message) {
return function (value) {
verify.number(value, message);
return value;
};
}
var verifyUnemptyString = _.partialRight(_.tap, verify.unemptyString);
Q('foo')
.then(verifyUnemptyString)
.then(print)
.then(verifyNumber('expect number on purpose'))
.then(console.log, onError);
results
1
2
3
4
5
6
7
8
$ node promises.js
foo
Error: expect number on purpose
at Object.number (node_modules/check-types/src/check-types.js:417:68)
at promises.js:15:12
From previous event:
at Object.<anonymous> (promises.js:25:2)
at node.js:901:3

Conclusion

Promises are powerful, and can be very concise and expressive. Even when writing very complex asynchronous code, you keep the power to tap into the flow to assert pre- or post- conditions or inspect passed values.