You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function co(fn) {
return function(done) {
var ctx = this;
var gen = fn.call(ctx);
var it = null;
function _next(err, res) {
it = gen.next(res);
if (it.done) {
done.call(ctx, err, it.value);
} else {
it.value(_next);
}
}
_next();
}
}
function co(fn) {
return function(done) {
var ctx = this;
var gen = fn.call(ctx);
var it = null;
function _next(err, res) {
it = gen.next(res);
if (it.done) {
done.call(ctx, err, it.value);
} else {
//new line
it.value = toThunk(it.value,ctx);
it.value(_next);
}
}
_next();
}
}
function objectToThunk(obj){
var ctx = this;
return function(done){
var keys = Object.keys(obj);
var results = new obj.constructor();
var length = keys.length;
var _run = function(fn,key){
fn.call(ctx,function(err,res){
results[key] = res;
--length || done(null, results);
})
}
foreach(var i in keys){
_run(Object[keys[i]],keys[i]);
}
}
}
co(function *(){
var a = size('.gitignore');
var b = size('package.json');
var r = yield [a,b];
return r;
})(function (err,args){
console.log("callback===args=======");
console.log(args);
})
/*
callback===args=======
[ 12, 1215 ]
*/
co(function *(){
var a = [size('.gitignore'), size('index.js')];
var b = [size('.gitignore'), size('index.js')];
var c = [size('.gitignore'), size('index.js')];
var d = yield [a, b, c];
console.log(d);
})()
function co(fn) {
return function(done) {
var ctx = this;
//old line
//var gen = fn.call(ctx);
//new line
var gen = isGenerator(fn) ? fn : fn.call(ctx);
var it = null;
function _next(err, res) {
it = gen.next(res);
if (it.done) {
done.call(ctx, err, it.value);
} else {
//new line
it.value = toThunk(it.value,ctx);
it.value(_next);
}
}
_next();
}
}
koa源码分析系列(二)co的实现
koa基于co实现,co又是使用了es6的generator特性,所以,没错这个特性支持很一般。
有下面几种办法体验generator:
##thunk函数
thunk函数是一个偏函数,执行它会得到一个新的只带一个回调参数的函数。下面我们对node的stat举个例子(其实是co官方的例子):
size函数就是个典型的thunk函数了,执行
size("./index.js")
我们就会得到一个只有回调的新函数。co的异步解决方案需要建立在thunk的基础上。##最简单的co实现
我们先看下有了co我们会怎么编程:
你会发现我们可以直接使用yield来直接获取 异步函数的值了。如果忽略yield关键字,完全就是同步编程了。再也不用考虑那一大堆回调了。co本质上也是一个thunk函数,接收一个generatorfunction作为参数,生成一个实际操作函数。这个实际操作函数可以接收一个callback来传入最后return的值。
下面我们就来实现最简单的co函数:
co本质上也是thunk函数,传入一个generatorFunction,它会自动帮你不停的调用对应generator的next函数,如果done为true代表generatorFunction函数执行完毕,就会把值传给回调函数。逻辑比较简单就不详细解释了。这边要注意_next函数的实现,注意11行,_next实际上会成为前面yield后面的函数的回调函数。
比如前面我们说的
size('package.json')
会返回一个带回调的函数a。于是调用就是yield a。这边11行it.value就会是这个a,会把_next作为回调执行a函数。所以这边需要有个约定就是thunk函数的回调都要是
function(err,res){}
的格式,实际上这也是node实际的规范。##进阶-yield后面跟array或者对象
上面我们实现了一个最简单的co函数,已经可以支持最基本的同步调用了,但是yield后面只能跟thunk函数的执行结果。我们这边还需要支持其他类型的yield值,比如一个数组或者对象。
我们要对co做些改进:
35行,我们增加了一行
it.value = toThunk(it.value,ctx);
用于对yield的值进行处理。我们看下
toThunk
的实现:toThunk
主要就是用来判断yield返回的值的类型,如果是对象或者数组就会调用objectToThunk
对返回值做处理。否则的话就会正常的返回。下面我们重点看看
objectToThunk
的实现方式。其实这种类型的函数基本都是一个思路。都是将数组里面所有的thunk函数全部拿出来执行一次,通过记录下数组的长度,各个函数执行一次就对公用的长度变量减一,不需要关心各个函数的执行顺序,只要当其中一个函数发现变量变为0时,代表其他函数都执行好了,我是最后一个,于是就可以调用回调函数done了。
objectToThunk
就是这种思路。首先我们先解释下面这两句的意思:
这么写是为了通用性,
Object.keys
接收一个数组或者对象,返回key值。eg:然后
new obj.constructor()
这句,会根据obj的类型生成一个相关的空数组或者空对象。便于下面的赋值。这也是动态语言的优势。之后我们定义了length变量,初始化为数组或者对象的属性长度。
然后就如上面的那个思路,挨个的使用_run执行每个函数,根据length来判断是否所有的函数都执行完毕了,执行完毕就调用回调函数done。
可以看到objectToThunk本质上也是一个thunk函数。这样 我们通过这层转换,使得数组里面的函数可以并行执行。
通过这层封装我们可以这么调用了:
yield后面跟的数组,两个异步任务,将会并行执行,不在乎谁先结束,而是等最慢的一个执行完成后会得到返回值赋值给r。
有的时候,可能会发生数组里面还是数组的情况,我们需要深度遍历执行。所以我们需要对上面的_run函数做下改造:
只要加一句
fn = toThunk(fn);
就成功实现了深度遍历了。不得不说TJ的设计真是太强大。这样 我们就可以这么调用了:
##进阶-yield后面跟promise,或者generator或generatorFunction
co的强大之处在于,yield真的几乎什么都可以跟了。promise是我们经常使用的解决异步的东西。我们现在如果想要支持yield后面跟promise对象,只需要做点小改动就行。
首先在toThunk里面加点东西
是的,只需要加一个针对promise的判断就行了。然后通过promiseToThunk来转换promise。
promiseToThunk
的实现也比较容易:还是通过转换,转成一个只有一个回调参数的函数。
那我们怎么去支持yield后面跟generator呢?
如果yield后面跟generator,我们期待的理想的结果是,继续执行这个generator里面的断点。其实有点类似es6规范里面yield的delegating yiled,不清楚的可以去看上一篇博文。co相当于做了这么个扩展。
首先我们继续在toThunk里面加一个判断
如果是generator的话 我们就直接调用co去处理。有木有觉得奇怪之前明明说co只接受
generatorFunction
来着。别急,让我们对co函数做点小改动:
仅仅一个简单的判断,于是世界都清净了,突然就可以yield后面跟generator对象了,就支持深度调用了。虽然有点绕,不过代码真的是太精辟了。
同样的如果我们要支持yield后面跟generatorFunction的话,只需要在toThunk里面再加一个判断:
如果是generatorFunction,我们就先执行得到generator再调用co处理。一切就是这么简单。
完整的代码如下:
这份代码,是去除了co里面很多判断,错误处理之后的代码。用来理解原理更加简单。
##结语
什么都不说了,co这样的库。源码不看真的是损失。是在不得不佩服TJ大神的脑子。据说以前还是个搞设计的。有了co,再也不用担心异步回调了。妈妈再也不用担心“恶魔金字塔了”so happy。。。。
The text was updated successfully, but these errors were encountered: