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:
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:
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.
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.
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.