A taste of nodejs generators

Playing with ES6 generators in Node.

Generators are coming to nodejs via ES6 promising to simplify async code

Node 0.11

Some features of JavaScript ES6 (Harmony) are available right now under nodejs 0.10.24. To see all available Harmony features, run:

:::sh
$ node --v8-options | grep harmony
    --harmony_typeof (enable harmony semantics for typeof)
    --harmony_scoping (enable harmony block scoping)
    --harmony_modules (enable harmony modules (implies block scoping))
    --harmony_proxies (enable harmony proxies)
    --harmony_collections (enable harmony collections (sets, maps, and weak maps))
    --harmony (enable all harmony features (except typeof))

I prefer running with all Harmony features turned on node --harmony

Generators are NOT available in node 0.10, so you need to run latest unstable version of node to get them. The best is to install multiple versions (0.10 and 0.11) in parallel using Node Version Manager

Install nvm using brew

brew install nvm

Load nvm script in your profile file, for example .bash_profile

# Load Node version manager
source $(brew --prefix nvm)/nvm.sh

Install two versions of Node using nvm

nvm install v0.11
nvm install v0.10
nvm ls

Now you can run a particular ES6 source file using

nvm run v0.11.10 --harmony <source file.js>

Simple generator example

Lets create an example that generates n*n sequence. In current EcmaScript5 it would be something like this:

function square() {
    var k = 0;
    return function genNextSquare() {
        return (++k)*k;
    };
}
var squares = square();
console.log(squares());
console.log(squares());
console.log(squares());

Note the use of closure to keep private instance of variable k. Same program using the new Generators feature in ES6:

function *square() {
  var k = 0;
  while (true) {
    yield (++k)*k;
  }
}
var squares = square();
console.log(squares.next().value);
console.log(squares.next().value);
console.log(squares.next().value);

Notice the parallels: square returns a generator function in both cases, variable k is private. The difference are less code, calling next and value, and having an infinite loop inside the generator function.

yield returns a value

Outside code calling next() can communicate with the generator via yield return value

function *square() {
  var k = 0;
  while (true) {
    k = yield (++k)*k;
    console.log('k =', k);
  }
}
var squares = square();
console.log(squares.next(10).value);
// prints 1, k = 20
console.log(squares.next(20).value);
// prints 441

Notice that everytime we called squares.next() we passed a value that was assigned to the variable k, and the generator function continued from yield statement. The continuation with outside value (provided by the caller when calling next()) is the key insight into how to use the generator functions.

returned value

Value returned from a generator is treated the same way as yielded.

function *square() {
    console.log('yielding 1');
    yield 1;
    console.log('returning 2');
    return 2;
}
var squares = square();
function logValues(generator) {
    console.log(generator.next());
    console.log(generator.next());
}
logValues(squares);
// prints
yielding 1
{ value: 1, done: false }
returning 2
{ value: 2, done: true }

next() throws an Error if generator has finished

If you try to call next() on a completed generator, it will throw an Error

function *square() {
    console.log('returning 2');
    return 2;
}
var squares = square();
function logValues(generator) {
    console.log(generator.next());
    console.log(generator.next());
}
logValues(squares);
// prints
returning 2
{ value: 2, done: true }
/Users/gbahmutov/git/training/node/es6/test2/index.js:8
    console.log(generator.next());
                          ^
Error: Generator has already finished

Generators and promises

Generator function returns an object with two properties: value and done. The later is boolean flag showing if the generator function has exited, for example by calling return. This is the key feature of generators allowing to simplify ES6 asynchronous code via promises.

An external function can call the generator function repeatedly, until done is true. If the generator function returns a promise, then external function can call the generator again with the resolved value, once the promise is resolved.

This makes the code inside the generator look and behave like synchronous code. Q.spawn is one such implementation. Lets compare code required to call async function and print result using promises vs generators

// common code
var Q = require('q');
function asyncSquare(n) {
  var defer = Q.defer();
  process.nextTick(function () {
    defer.resolve(n*n);
  });
  return defer.promise;
}
// ES5 using promises
asyncSquare(5)
.then(console.log)
.then(function () {
    return asyncSquare(6);
})
.then(console.log)
.done(function () {
  console.log('all done');
});
// ES6 using generator that yields promises to Q.spawn
Q.spawn(function *() {
  console.log(yield asyncSquare(5));
  console.log(yield asyncSquare(6));
  console.log('all done');
});
// logical flow
// Q grabs promise, waits until it is resolved, then calls next(25)
// 25 is passed to console.log
// Q grabs promise, waits until it is resolved, then calls next(36)
// 36 is passed to console.log

Same code, but looks much, much cleaner!

Future

For more examples look at Promises and Generators: an utopia slides. For real world example using generators to simplify async code, see Experiments with Koa and JavaScript generators

See "part 2: A second taste of nodejs generators"