Async using generators

Asynchronous processing using ES6 generators.

Related:

My primary interest in EcmaScript6 generator functions is their ability to drastically simplify asynchronous code. For example, if we have an async function computing square of a number, we might compute and print several values using callbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function asyncSquare(n, cb) {
process.nextTick(function () {
cb(n*n);
});
}
function done() {
console.log('all done');
}
// pyramid of doom
asyncSquare(2, function (result) {
console.log(result);
asyncSquare(3, function (result) {
console.log(result);
asyncSquare(4, function (result) {
console.log(result);
done();
});
});
});
// prints
4
9
16
all done

The callbacks form the infamous pyramid of doom, are tightly coupled, and even without any error handling it is hard to understand what is going on here.

Using helper modules

I can transform the pyramid of doom using one of the numerous async packages for nodejs, for example step

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var Step = require('step');
function asyncSquare(n, cb) {
process.nextTick(function () {
cb(n*n);
});
}
function done() {
console.log('all done');
}
Step(
function () {
asyncSquare(2, this)
},
function (result) {
console.log(result);
this();
},
function () {
asyncSquare(3, this)
},
function (result) {
console.log(result);
this();
},
function () {
asyncSquare(4, this)
},
function (result) {
console.log(result);
this();
},
function () {
done();
}
);
// prints
4
9
16
all done

This makes the logical sequence more obvious, but there is a LOT of boilerplate, plus there is extra complexity (binding this to Step function for example).

Using promises

Promises are my standard goto way of dealing with asynchronous code. I prefer using library Q, works under node and browser, and its lite version is even included with AngularJs (as $q). The same code could be ordered very nicely:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Q = require('q');
function asyncSquare(n) {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve(n*n);
});
return defer.promise;
}
function done() {
console.log('all done');
}
asyncSquare(2)
.then(console.log)
.then(asyncSquare.bind(null, 3))
.then(console.log)
.then(asyncSquare.bind(null, 4))
.then(console.log)
.done(done);
// prints
4
9
16
all done

The sequence of operations is now very clear (compute, print, compute, print, etc). The only downside is the difference in calling the asyncSquare method the first time vs the other times, because we want to pass parameters and start the asyncSquare after previous step finishes.

Using generators

Finally, lets rewrite this using generators. The async to sync transformation will be performed by the caller, I will use Q.spawn to control the execution flow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Q = require('q');
function asyncSquare(n) {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve(n*n);
});
return defer.promise;
}
function done() {
console.log('all done');
}
Q.spawn(function *() {
console.log(yield asyncSquare(2));
console.log(yield asyncSquare(3));
console.log(yield asyncSquare(4));
done();
});
// prints
4
9
16
all done

It is useful to try to step through the logical flow in order to comprehend what is done here.

1
2
3
4
5
6
7
8
Q.spawn(function *() {
yield asyncSquare(2) // yields a promise returned by asyncSquare to Q
// Q waits for promise to be resolved (asynchronously)
// then calls next(resolved value)
// the generator function resumes
console.log(resolved value)
...
}

Q.spawn is not the only good caller function for async processing. TJ Holowaychuk has created Node module co that has the same logic and works very cleanly with functions yielding promises or callbacks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Q = require('q');
var co = require('co');
function asyncSquare(n) {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve(n*n);
});
return defer.promise;
}
function done() {
console.log('all done');
}
co(function *() {
console.log(yield asyncSquare(2));
console.log(yield asyncSquare(3));
console.log(yield asyncSquare(4));
})(done);
// prints
4
9
16
all done

Q.async

I like co for only one reason - it has explicit done callback place, while Q.spawn does not. It would be nice if Q.spawn returned a promise, making chaining it to other code easier! I believe this is the intended functionality of Q.async, still marked as experimental feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Q = require('q');
function asyncSquare(n) {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve(n*n);
});
return defer.promise;
}
function done() {
console.log('all done');
}
var printSquares = Q.async(function *() {
console.log(yield asyncSquare(2));
console.log(yield asyncSquare(3));
console.log(yield asyncSquare(4));
})();
printSquares.then(done);
// prints
4
9
16
all done

Update

I found why the Q.spawn is different from Q.async - the way they handle errors (throw them or swallow quietly). This is explained by James Long, the author of Q.spawn in his blog post. You should read the post in any case, since it shows several examples fighting callback hell and explains the matter very clearly.