起步学node比较晚,因此没看express,直接看了koa,可是多数组件都是经过callback回调来执行,没法直接在koa中使用,因此了解了下koa的实现原理,以便包装现有组件或方法,这里记录下来细节,以做备份,并给你们作个参考吧。html
koa依赖co,配合thunkify来执行,虽然co如今已经改为基于promise实现了,但其实原理基本差很少,我这里先分析下co3.x版本。node
( 这里须要你对 generator 有必定了解 )es6
核心是利用generator能够挂起或next(res)来继续执行方法的特性。express
这里不考虑异常处理流程,结合适当伪码,给出一个简易的demo便于理解co的控制流程:json
常规异步嵌套代码:api
fs.readFile('template.html', function (err, template) { // 读取模板 asyncFunc('data.json', function (err, data) { // 获取数据 rep.end(render(template, data)); // 装配并返回 }); });
(注:上面的代码仅做示意,并不考虑效率因素。实际上若是模板和数据都是异步的,咱们应该同时发起请求,co推荐将异步操做组成数组或对象,它在内部会作平行处理以便优化速度)数组
咱们指望的同步写法:promise
var tempalte = readFile('template.html'); var data = asyncFunc('data.json'); rep.end(render(template, data));
为了实现这个功能,咱们利用generator的特性,先作一个工具方法迭代generator:app
function core(genfunc) { var g = genfunc(); (function next(err, res) { res = g.next(res); if (!res.done) { res.value(next); } })(); }
上面这个方法接收一个generatorFunction做为参数,而后实例化它生成迭代器g,next方法不停的反复调用g,并将上一次的返回值做为参数不停的传递下去(有点像promise链式调用)。这里有个地方注意:生成器每次返回的值必须是一个方法,并接受fn回调参数,这样next方法才能做为参数不停的迭代下去(co支持多种数据类型,它自行包装了这些数据类型,最终仍是返回方法)。koa
那么下面就差最后一步了,包装node标准api中的异步方法,以便支持yield:
function thunkify(fn) { return function () { var args = Array.prototype.slice.call(arguments, 0); return function (next) { fn.apply(this, args.concat(next)); } } } // 包装原生API var readFile = thunkify(fs.readFile);
如今当调用readFile('path')的时候返回的是一个方法了,也就是yield的返回值,经过在core方法中,咱们来调用这个方法,并将next做为参数传递给它,因而整个流程就能够以同步方式执行下去了,咱们将刚才的同步代码经过core进行包装,最终获得:
core(function*() { var tempalte = yield readFile('template.html'); var data = yield asyncFunc('data.json'); rep.end(render(template, data)); });
这里asyncFunc我用来表示任何异步方法,它应该与readFile相似,返回另外一个方法并接受callback回调。标准API一般能够经过thunkify来直接wrap,但若是是本身实现的方法必需要遵照这个约定。
以上代码依赖generator特性,须要安装最新版的node,当前最新版本是0.11.15,经过--harmony参数启动,推荐安装iojs,支持es6多数语法,用来学习了解新语法不错。