Harmony generator , for-of and co

Begin

We talk about the new stuff with harmony, so make sure your harmony mode is enabled. With Chrome, go chrome://flags/, search harmony and enable it. With node, you need 0.11.* version, and execute node --harmony. For me, I use n to manage my node version, which is powered by TJ.

Generator

These days I was diving into Harmony generator, it is defined in ES6. So what calls generator? Let's begin with a simple code.

function* gen() {
    var index = 0;
    while(true) {
        yield index++;
    }
}
var gen = gen();
gen.next(); // return {value: 0, done: false}
gen.next(); // return {value: 1, done: false}
//...

As you can see, function gen has a method called next. This method returns an object with two properties: value and done. The value can be any value, and the done will be read as a boolean to distinguish whether the iterator has already to the end.

A simple iterator implement:

function gen(array) {
    var index = 0;
    return {
        next: function () {
            return index < array.length ?
                {vale: array[index++], done: false} :
                {done: true};
        }
    }
}

You may also notice the grammer function*, that's called generator function, and you can use keyword field there, which works combined with next(). When next is invoked, it starts the execution of the generator. The generator runs until it encounters a yield expression. Then it pauses and the execution goes back to the code that called next.

You can also pass parameters to next(). It behaves like this:

function* gen() {
    var index = 0;
    while (true) {
        var res = yield index++;
        console.log(res);
    }
}
/**  yield and next
 * gen() is invoked by a next.
 * when execute gen.next(88), the generator runs until
 * it encounter a yield and pauses, but not give the value
 * to res. when execute gen.next(888), it goes back to the
 * code and give the value to res, and the value is the 
 * parameter passed to next.
 */
var gen = gen(); // gen() is not invoked there
gen.next(88); // {value: 1, done: false} 
gen.next(888); // 888

It is like another way to execute callback. However, we can code synchronously to express the asynchronous progress without nested function.

for-of

This expression is used to iterate over iterable objects(including Array, Map, Set, arguments object and so on). Just a generator comprehension.

for (let i of [1, 2, 3]) {
    console.log(i * i); 
}
// 1, 4, 9

co

co is an ultimate generator made by TJ, using thunks or promises. A simple co example:

var co = require('co');
var fs = require('fs');

function read(file) {
  return function(fn){
    fs.readFile(file, 'utf8', fn);
  }
}

co(function *(){
  var a = yield read('1.txt');
  var b = yield read('2.txt');
  var c = yield read('3.txt');
  console.log([a,b,c]);
})();

We pass a generator fn to co and co return a thunk. A thunk is just like the traditional node-style callback whith a signature of: (err, res).

// read thunk
function read(path, encoding) {
    return function (cb) {
        fs.readFile(path, encoding, cb);
    }
}
//invoke
read('package.json', 'utf8')(function(err, str){
    //...
})

Actually, in co, the yieldable objects supports thunks, promises, generators, generator functions and array. co will iterate many times until the fn.done is true. A most simple co implement:

function co(GenFunc) {
  return function(cb) {
    var gen = GenFunc();
    (function next() {
        if (gen.next) {
            gen.next().done ? cb && cb() : next();
        }
    })();
  }
}

co also support passing arguments into the generator.

var exec = require('co-exec');
co(function *(cmd) {
  var res = yield exec(cmd);
  return res;
})('pwd', done); // done is the callback function

co support error handle as well. And in github, it says:

Co is careful to relay any errors that occur back to the generator, including those within the thunk, or from the thunk's callback.

At last, I'm strongly recommend that you should read co's source code, it is less than 300 lines. Have fun:D

Reference