This post assumes you already know about Promises, Generators and aims at focusing more on the co
library and the generator based workflow it supports. If you are not very familiar with Promises or Generators, I would recommend you study them first.
Promises are nice
Promises can save us from the callback hell and allow us to write easy to understand codes. May be something like this:
1 2 3 4 5 6 7 8 9 |
function promisedOne() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 3000); }) } promisedOne().then(console.log); |
Here we have a promisedOne
function that returns a Promise
. The promise is resolved after 3 seconds and the value is set to 1. For keeping it simple, we used setTimeout
. We can imagine other use cases like network operations where promises can be handy instead of callbacks.
Generator Based Workflow
With the new ES2015 generators and a nice generator based workflow library like co
(https://github.com/tj/co) , we can do better. Something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const co = require('co'); function promisedOne() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 3000); }) } function * main() { try { let promised_value = yield promisedOne(); console.log(promised_value) } catch (e) { console.error(e); } } co(main) |
What’s happening here? The co
function takes a generator function and executes it. Whenever the generator function yield
s something, co
checks if the object is one of the yieldables
that it supports. In our case, it’s a Promise
which is supported. So co
takes the yielded promise, processes it and returns the results back into the generator. So we can grab the value from the yield expression. If the promise is rejected or any error occurs, it’s thrown back to the generator function as well. So we could catch the error.
co
returns a Promise
. So if the generator function returns any values for us, we can retrieve those using the promise APIs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function * returnSomething() { return "Done" } co(returnSomething).then(console.log); // Or co(function * () { return "Something else" }).then(console.log); // Let's make it a little more complex co(function *() { return yield new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 3000); }) }).then(console.log); |
So basically, we yield from the yieldables
, catch any errors and return the values. We don’t have to worry about how the generator is working behind the scene.
The co
library also has an useful function – co.wrap
. This function takes a generator function but instead of executing it directly, it returns a general function which returns a promise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const co = require('co'); function promisedOne() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 3000); }) } const main = co.wrap(function *() { return yield promisedOne(); }); // main function now returns promise main().then(console.log); |
This can often come handy when we want to use co
with other libraries/frameworks which don’t support generator based workflow. For example, here’s a Gist that demonstrates how to use generators with the HapiJS web framework – https://gist.github.com/grabbou/ead3e217a5e445929f14. The route handlers are written using generator function and then adapted using co.wrap
for Hapi.