精华
搞定 koa 之 co源码
书接上文,这次我们来详细看看 co 的源码,这是了解 koa 的必要步骤。
系列目录
在看源码前,我们再看段 generator 的代码
function* Gen(a){
var b = yield a;
console.log(b);//3
var c = yield b;
yield c;
}
var g = Gen(1);
console.log(g.next(2));//{ value: 1, done: false }
console.log(g.next(3));//{ value: 3, done: false }
console.log(g.next(4));//{ value: 4, done: false }
console.log(g.next());//{ value: undefined, done: true }
- 有点奇怪的结果,传入的2没有被返回
- 调用 Gen 的时候,会为 a 初始化一个值,所以 g.next(2)返回1
- 调用 g.next(3)的时候会为 b 赋值3,也就是说调用 g.next(2)的时候并没有为 b 赋值,只有再次调用 next,函数才会继续运行,执行到 b 赋值的部分。
看懂了上面的代码,再看看 co 是如何用的
var co = require('..');
var fs = require('fs');
function read(file) {
return function(fn){
fs.readFile(file, 'utf8', fn);
}
}
co(function *(){
var a = yield read('.gitignore');
var b = yield read('Makefile');
var c = yield read('package.json');
console.log(a.length);
console.log(b.length);
console.log(c.length);
});
- co 需要传入一个 generatorFunction
- co 的原理很简单,就是利用 next 逐步执行以及可以给 next 传参数来为变量赋值
- 例如上面的代码,开始co执行generatorFunction,然后调用next,就可以获得 read(’.gitignore’)的返回值。这里的 read 会返回一个函数。
- co 会往这个函数里面传入一个回调函数,readFile 完成时,回调函数会被触发,这个回调函数的逻辑就是调用 next(data)。这样 a 变量就会获得 readFile 返回的结果了。
看懂上面的代码,我们正式进入 co 的源码
function co(fn) {
//判断是否为 generatorFunction
var isGenFun = isGeneratorFunction(fn);
return function (done) {
var ctx = this;
// in toThunk() below we invoke co()
// with a generator, so optimize for
// this case
var gen = fn;
// we only need to parse the arguments
// if gen is a generator function.
if (isGenFun) {
//把 arguments 转换成数组
var args = slice.call(arguments), len = args.length;
//根据最后一个参数是否为函数,判断是否存在回掉函数
var hasCallback = len && 'function' == typeof args[len - 1];
done = hasCallback ? args.pop() : error;
//执行 generatorFunction
gen = fn.apply(this, args);
} else {
done = done || error;
}
//调用 next 函数,这是一个递归函数
next();
// #92
// wrap the callback in a setImmediate
// so that any of its errors aren't caught by `co`
function exit(err, res) {
setImmediate(function(){
done.call(ctx, err, res);
});
}
function next(err, res) {
var ret;
// multiple args
if (arguments.length > 2) res = slice.call(arguments, 1);
// error
if (err) {
try {
ret = gen.throw(err);
} catch (e) {
return exit(e);
}
}
// ok
if (!err) {
try {
//执行 next,会获得 yield 返回的对象。同时通过 next 传入数据,为变量赋值
//返回的对象格式是{value:xxx,done:xxx},这里的 value 是一个函数
ret = gen.next(res);
} catch (e) {
return exit(e);
}
}
// done 判断是否完成
if (ret.done) return exit(null, ret.value);
// normalize
ret.value = toThunk(ret.value, ctx);
// run
if ('function' == typeof ret.value) {
var called = false;
try {
//执行 ret.value 函数,同时传入一个回调函数。当异步函数执行完,会递归 next
//next又会执行gen.next(),同时把结果传出去
ret.value.call(ctx, function(){
if (called) return;
called = true;
next.apply(ctx, arguments);
});
} catch (e) {
setImmediate(function(){
if (called) return;
called = true;
next(e);
});
}
return;
}
// invalid
next(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following was passed: "' + String(ret.value) + '"'));
}
}
}
参考资料
联系我
5 回复
说2点markdown用法吧
- 标题里的## 后面要加一个空格,这是因为cnodejs用的markdown编译器不支持不带空格的写法
- 不要从2级标题【参考资料】跳到4级标题【联系我】,标题要有连续性
@i5ting 感谢 已改
http://purplebamboo.github.io/2014/05/24/koa-source-analytics-1/ 我也写过一个系列,跟楼主一样。。按照 generator co koa的顺序 挨个的介绍。。
@purplebamboo 写得很赞,早知道看你的就好啦
受教了