Created
May 23, 2014 16:20
-
-
Save jedrichards/3e9a2ac6587c2f5985cd to your computer and use it in GitHub Desktop.
Revisions
-
Jed Richards created this gist
May 23, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,58 @@ # 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 This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,12 @@ 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 ) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,16 @@ 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); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,19 @@ 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); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,24 @@ 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)(); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,33 @@ 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)();