co是一个使
Generator
自动执行的函数库,设计的非常精妙。
如果不知道
Generator
是什么,请看阮一峰的ECMAScript 6入门
koa的中间件实现就是依赖了co,使处理异步代码写的像同步代码一样,摆脱了回调地狱
博客地址
co文件非常小,加上注释就240行,核心代码就几十行,其他都是一些辅助函数,比如判段类型和和将array object
等转化成promise
这里我将这都算在toPromise
函数内。
所以不算这些函数的话,实际上用上函数的就这只有co, onFulFilled, next, toPromise
核心代码如下
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1);
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
return null;
}
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
这里通过co函数执行的gen
的代码分析一下co函数的流程
var co = require('./');
function sleep(ms) {
return function(done){
setTimeout(done, ms);
}
}
function *work() {
yield sleep(50);
return 'yay';
}
function* gen() {
var a = yield work;
var b = yield work;
var c = yield work;
return a + b + c;
}
co(gen).then((data) => console.log(data))
这里的代码选自co
的测试用例
co
函数传入一个Generator
类型的gen
并返回Promise
在yield
后面的表达式或者说异步操作都执行结束后,提供一个钩子处理函数的返回值。
Generator
自执行通过两个函数onFulfilled和next配合实现,首先一开始会执行一次onFulfilled()
使gen
函数开始执行
onFulfilled源码如下
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
console.log(ret);
next(ret);
return null;
}
作用主要是为了捕获异常和执行gen.next
,如果代码下一次next操作没有问题,则交给next函数
处理,反之将异常作为reject
抛出,在这里 第一次执行后会在var a = yield work
这里暂停, ret = gen.next(res)
执行后 ret会得到的将会是
{ value: [Function: work], done: false } //并作为next参数执行next(ret)
next
主要判定Generator
是否已经执行结束,如果结束返回,反之判断还未结束将对yield
后面的表达式转成promise
然后继续执行onFulfilled
,
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
在这里 ret.done === false
所以将对work
转化成promise
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
代码如上,通过类型判断执行具体转化函数, 具体可以去看源码, 另外在next
函数也写有目前支持在yield
的表达式类型为 a function, promise, generator, array, or object
回到next
函数在这里将work
包装成Promise
并在Promise
执行成功后执行onFulfilled
在这里work
的类型是generator
所以co
里实际上执行了co(work)
我们测试代码第二次执行onFulfilled
与第一次不同的是,第二次会带有Promise
返回的值执行,而这个值实际上就是work
生成器执行结束后return
的值在这里就是'yay'
ret = gen.next(res); // 第二次执行`onFulfilled`后gen.next(res)中的res 就等于'yay';
var a = yield work; //所以在gen.next(res)执行后 a = 'yay'
var b = yield work; //开始执行 var b = yield work;
如此重复到第四次,因为var c = yield work;
执行完后已经没有下一个yield
,所以第四次执行gen.next
函数返回的ret.done === true
并将return a + b + c;
作为Promise
的resolve
返回。
所以大概流程如下
- 开始执行
gen函数
, 遇到yield
暂停,异步处理yield
后面的表达式 - 表达式执行结束后,返回执行结果(resolve),继续开始执行下一步操作
- 继续1的操作,直到函数结束