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 | function asyncSquare(n, cb) { |
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 | var Step = require('step'); |
// 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 | var Q = require('q'); |
// 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 | var Q = require('q'); |
// 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 | Q.spawn(function *() { |
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 | var Q = require('q'); |
// 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 | var Q = require('q'); |
// 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.