co函数库 是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
Generator函数返回的是一个Iterator遍历器,遍历器是一种可遍历的数据类型,不能自动遍历,也不能很好的控制 yield 后面的异步操作流程,所以co函数用于解决这个问题,将generator变成异步流程控制的解决方案。
变化
co4.x.x
最大的变化是 yield 后推荐使用 Promise
,co()
返回的函数只会是 Promise ,不再返回thunk函数了,内部也主要使用 Promise
。
基本用法
yield后面可接受的语句类型
promise对象 |
串行执行
从上至下,一步一步的同步化执行并返回结果co(function*(){
var r1 = yield Promise.resolve(1)
console.log(r1)
var r2 = yield Promise.resolve(2)
console.log(r2)
var r3 = yield Promise.resolve(3)
console.log(r3)
return [r1, r2, r3]
}).then(function(res){
console.log(res)//[ 1, 2, 3 ]
}).catch(function(e){
console.log(e)
})
co(function* (){
var t1 = new Date().getTime();
var d1 = yield asyncFn(1, 1000)
var t2 = new Date().getTime();
console.log(d1, t2-t1)
//yield后面跟Generator函数
var d2 = yield function*(){ return yield asyncFn(2, 2000)}
var t3 = new Date().getTime();
console.log(d2, t3-t2)
//yield后面跟Generator对象
var d3 = yield (function*(){ return yield asyncFn(3, 3000)})()
var t4 = new Date().getTime();
console.log(d3, t4-t3)
})
// data: 1 1004
// data: 2 2027
// data: 3 3007
并发执行
yield 后面跟数组co(function* (){
return yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]
}).then(res => console.log(res), err => console.log(err))
// [1, 2, 3]
yield后面跟对象
对象中的值,如果不是Promise对象,会被包装成Promise对象并返回co(function* (){
return yield {
d1: Promise.resolve(1),
d2: Promise.resolve(2),
d3: 3
}
}).then(res => console.log(res), err => console.log(err))
//{ d1: 1, d2: 2, d3: 3 }
将生成器转换为普通函数一样使用
将 Generator函数 转换为 普通函数,但内部依然按照Generator函数执行方式执行
应用于需要普通回调函数的地方,如数组的forEach
、 reduce
等,事件注册和Node中大多数API都需要普通的回调函数的地方
function asyncFn(num, time){ |
其实并没有真正的转换,而是直接执行的co()函数,并将forEach回调的参数传递给了Generator函数。
源码结构
主要内容
源码主要包含两个主要的方法和一些工具函数
一个方法 co.wrap
一个主函数 co
八个工具函数
toPromise [Function]
调用下面的工具函数thunkToPromise [Function]
将thunk函数包装为promise对象arrayToPromise [Function]
将数组中的元素包装为promise对象objectToPromise [Function]
将对象中的值包装为promise对象isPromise [Function]
判断是否为promise对象isGenerator [Function]
判断是否为generator对象isGeneratorFunction [Function]
判断是否为generator函数isObject [Function]
判断是否为原生的对象
模块导出的兼容性处理
module.exports = co['default'] = co.co = co; |
这种方式的好处是可以满足下面这些导入方式
var co = require('co') |
主函数
主要看 return
后面语句function co(gen){
// 使用Promise将返回值用包装后返回
return new Promise(function(resolve, reject) {
//...
});
}
co()
的返回值,使用了Promise包装对象,始终返回一个 promise
对象
第一句if (typeof gen === 'function') gen = gen.apply(ctx, args);
如果第一个参数是函数,则将第一个后面的参数传递给这个函数,并执行一次这个函数,所以这里gen即可以是Generator函数,也可以是普通函数,如果是Generator函数,那么gen就是Generator对象,如果是普通函数,返回值就是普通数据。
当使用 co.wrap()
时,实际上是执行了co()函数,并将参数传递给了gen函数
第二句
当执行了gen函数后,是否有返回值,返回值是否有next方法,如果都没有,则直接返回结果。说明非Generator对象if (!gen || typeof gen.next !== 'function') return resolve(gen);
第三句
到这里,就基本可以确定,是Generator对象了,第一次调用一下next方法,遍历第一个yield, 遍历完成之后,调用内部私有方法next()onFulfilled();
内部的next方法
这个核心方法就是为了递归遍历Generator对象,直到返回 { value:xxx, done:true }
,遍历完成。function next(ret) {
// 遍历完成,返回结果
if (ret.done) return resolve(ret.value);
// 将遍历器返回结果中的value包装成promise对象
var value = toPromise.call(ctx, ret.value);
// 如果value用promise包装成功,则调用promise,处理返回成功或失败的逻辑
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 不合法的数据,返回警告信息:yield后面只能跟 promise/generator/array/object 类型的数据
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
onFulfilled 与 onRejected
这两个方法,故名思义,就是处理Promise返回状态的回调函数
onFulfilled是promise成功后的回调,用于遍历下一个yield,完成后将遍历结果传递给内部的next方法去判断是否继续遍历,function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
// 如果调用next()方法出错,则返回错误
return reject(e);
}
// 处理返回后的结果{value: PromiseObj, done: false/ture}
// 遍历下一个yield
next(ret);
}
onRejected
先处理异常错误,将错误抛出,
如果yield使用了 try...catch
,那么不影响后面的遍历,gen.throw()
之后会自动调用一次next方法返回遍历结果。
如果yield没有使用 try...catch
, 那么错误会被 reject
出去,然后被 co().catch()
捕获,中断遍历。
最后将结果传递给内部的 next(ret)
,判断是否继续遍历
function onRejected(err) { |
几个重要的工具函数
thunkToPromise & arrayToPromise & objectToPromise
thunkToPromise
将thunk函数使用Promise包装一下, 实际上就是执行thunk函数,将结果使用Promise包装返回promise对象。
function thunkToPromise(fn) { |
arrayToPromise
将数组时的每一个元素转成Promise对象,然后使用Promise.all()并行执行,并返回另一个Promise对象。function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}
objectToPromise
对象的处理稍微要复杂一些
先将对象中的值转换成Promise对象,再将这些Promise对象执行并推入到promises数组,使用Promise.all()执行这个数组,保证全部执行完成,每一个数组中的promise对象返回成功时,把结果存储到object对应的key。
function objectToPromise(obj){ |
详细的源码解析
** |