Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
generators

ES6 Generators

  • Available in ES6 Harmony
  • Present in Node 0.11 unstable and above
  • Enable via the --harmony or --harmony-generators flag in 0.11

Intro

Generators are constructor functions for iterators.

Define an iterator constructor function (i.e. a generator) with a * after the function keyword.

The iterator instance returned from a generator function has a method next.

The next method can be called multiple times on a generator, and each time it will return the result of each consecutuve iteration.

Use the yield keyword inside the generator to emit this result.

Every time next is called the function is invoked and it returns an object with value and done fields. The value field contains whatever was yielded, and the boolean done indicates whether the function has returned/completed yet or not.

Importantly unlike normal non-blocking JS, execution inside the generator function is blocked immediately after a yield, and only resumes when next is called again.

Example: iterator.js

Async flow control with generators

Example: 'Callback hell' https://github.com/jedrichards/portfolio/blob/master/api/api/routes/auth-routes.js

next returns its yielded value synchronously, there's nothing intrinsically async about generators.

But where things get interesting is when the yielded value is something other than a primitive value. For example some sort of object that represents an async operation like a promise or a thunk.

When used in this way, usually together with a generator flow control library like co, we can describe sets of sequential async operations that look like sync code and thus avoid messy nested callbacks and fiddly error handling

Example: thunk.js

The above example works because co can recognise a thunk being yielded and knows how to handle it. The full list of yielded objects that co can work with:

  • Thunks
  • Promises
  • More generators (nesting)
  • Arrays & objects (for parallel execution)

Example: parallel.js

KoaJS

Flow control using generators turns out to be a nice pattern for web app middleware.

We can use the yield keyword to avoid nested callbacks in middleware (for example when interacting with other async resources), and create an 'onion skin' like structure. Each middleware yields to the next until a middleware function returns at which point the response is sent and the middleware stack unwinds allowing each middleware to perform any final "post response" actions. Middleware state is retained pre and post response by lieu of the simple fact we're still in the same function scope.

Example: koa-1.js

KoaJS uses co under the hood so we can take advantage of the flow control it provides to construct nested sets of sequential or parallel middleware tasks using thunks, promises and generator syntax.

Error handling is also much improved. No more messy testing null and err function arguments like traditional callbacks. Generator and yield syntax means that the call stack is not disrupted like with async code (remember that inside generators syncronous code is just blocked, not left behind) this means that try catch blocks work as expected.

Example: koa-2.js

function* Iterator (x) {
for ( var i=0; i<x; i++) {
yield i;
}
}
var iterator = Iterator(10);
do {
var res = iterator.next();
console.log(res);
} while ( !res.done )
var koa = require('koa');
var server = koa();
server.use(function* logger (next) {
var start = Date.now();
yield next;
var ms = Date.now() - start;
console.log('%s %s %s (%sms)',this.method,this.url,this.status,ms);
});
server.use(function* hello (next) {
this.body = 'hello';
});
server.listen(8000);
var util = require('util');
var koa = require('koa');
var co = require('co');
var nano = require('nano')('https://isaacs.iriscouch.com');
var conano = require('co-nano')(nano);
var db = conano.use('registry');
var server = koa();
server.use(function* queryNpm (next) {
try {
var desc1 = (yield db.get('rsyncwrapper'))[0].description;
var desc2 = (yield db.get('grunt-rsync'))[0].description;
this.body = util.format('<pre>%s</pre><pre>%s</pre>',desc1,desc2);
} catch (err) {
this.status = 500;
}
});
server.listen(8000);
var thunkify = require('thunkify');
var request = require('request');
var get = thunkify(request.get);
var co = require('co');
var cheerio = require('cheerio');
function* scrape () {
var thunks = [
get('.'),
get('http://www.bbc.co.uk/news'),
get('http://www.twitter.com')
];
try {
(yield thunks).forEach(function (res) {
console.log(cheerio.load(res.body)('title').text());
});
} catch (err) {
console.log('hi');
}
}
co(scrape)();
var co = require('co');
function fibonacci (n) {
return n<2?n:fibonacci(n-1)+fibonacci(n-2);
}
function fibonacciAsync (n,cb) {
setTimeout(function () {
cb(null,fibonacci(n));
},500);
}
// A thunk is a traditional callback-style async function wrapped in a closure
// that 'bakes in' any values passed to the function while exposing only the
// callback itself outside of the closure for invoking later.
//
// Essentially it separates out passing in the parameters into an async function
// (which your code defines) from actually invoking the async function with a
// callback (which a flow control library can handle without any prior knowledge).
function fibonacciAsyncThunk (x) {
return function (cb) {
fibonacciAsync(x,cb);
}
}
function* fibToTen () {
for ( var i=1; i<11; i++ ) {
console.log(yield fibonacciAsyncThunk(i));
}
}
co(fibToTen)();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.