co4.6.0 源码解析

co函数库 是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。

Generator函数返回的是一个Iterator遍历器,遍历器是一种可遍历的数据类型,不能自动遍历,也不能很好的控制 yield 后面的异步操作流程,所以co函数用于解决这个问题,将generator变成异步流程控制的解决方案。

变化

co4.x.x 最大的变化是 yield 后推荐使用 Promiseco() 返回的函数只会是 Promise ,不再返回thunk函数了,内部也主要使用 Promise

基本用法

yield后面可接受的语句类型

promise对象
thunks函数(一个偏函数,执行之后只有一个简单的拥有一个callback的参数的函数)
array数组(用于并行执行数组中的promise
objects对象(用于并行执行对象中的promise
generator对象
generator函数

串行执行

从上至下,一步一步的同步化执行并返回结果

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函数执行方式执行

应用于需要普通回调函数的地方,如数组的forEachreduce 等,事件注册和Node中大多数API都需要普通的回调函数的地方

function asyncFn(num, time){
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve('data: '+num)
},time)
})
}
var res = [1, 2, 3].forEach(co.wrap(function* (item, i){
var d = yield asyncFn(item, i*1000)
console.log(d)
}))

其实并没有真正的转换,而是直接执行的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')
var wrap = co.wrap
require('co').co
import co from 'co'
import { wrap, co } form 'co'
import * as co from '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) {
var ret;
try {
// 抛出错误信息,利用了generator对象在外部抛出的错误可以被内部捕获
// 如果yield被try...catch包裹,就会被catch捕获,不影响遍历,throw后自动调用next遍历一次
ret = gen.throw(err);
} catch (e) {
// 如果内部的yield没有被try...catch, 则会在co().catch()中捕获
return reject(e);
}
// 继续遍历下一个yield
next(ret);
}

几个重要的工具函数

thunkToPromise & arrayToPromise & objectToPromise

thunkToPromise

将thunk函数使用Promise包装一下, 实际上就是执行thunk函数,将结果使用Promise包装返回promise对象。

function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
fn.call(ctx, function (err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}

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){
// 构造一个Object对象
var results = new obj.constructor();
// 获取所有的key
var keys = Object.keys(obj);
var promises = [];
//将obj对象中的值全部转换为promise对象
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]);
//转换成功,则将值替换为promise对象,
//这里很巧妙,调用defer执行了promise对象,并将结果存储到了results中,并返回了一个promise对象存储到了promises数组中
if (promise && isPromise(promise)) defer(promise, key);
//转换失败,则原值不变
else results[key] = obj[key];
}

// 调用Promise.all并发执行刚刚生成的promises数组,并将结果返回
return Promise.all(promises).then(function () {
return results;
});

function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
//执行obj中使用promise包装后的value, 并返回一个promise对象存储到数组中,用于Promise.all调用
promises.push(promise.then(function (res) {
results[key] = res;
}));
}
}

详细的源码解析

**
* slice() reference.
*/
// slice 方法的缓存
var slice = Array.prototype.slice;

/**
* Expose `co`.
*/
// 兼容ES6与ES5模块导出
// var co = require('co')
// var wrap = co.wrap
// require('co').co
// import co from 'co'
// import { wrap, co } form 'co'
// import * as co from 'co'
module.exports = co['default'] = co.co = co;

/**
* Wrap the given generator `fn*` into a
* function that returns a promise.
* This is a separate function so that
* every `co()` call doesn't create a new,
* unnecessary closure.
*
* @param {GeneratorFunction} fn
* @return {Function}
* @api public
*/
// 将Generator函数转换为普通函数
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
//调用co函数,执行fn,并返回结果
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};

/**
* Execute the generator function or a generator
* and return a promise.
*
* @param {Function} fn
* @return {Promise}
* @api public
*/
// 执行generator函数或generator对象,返回一个promise对象
function co(gen) {
// 缓存当前上下文
var ctx = this;
// 获取第二个参数
var args = slice.call(arguments, 1)

// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
// 使用Promise将返回值用包装后返回
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();

/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
// 执行遍历器
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
// 如果调用next()方法出错,则返回错误
return reject(e);
}
// 处理返回后的结果{value: PromiseObj, done: false/ture}
// 遍历下一个yield
next(ret);
}

/**
* @param {Error} err
* @return {Promise}
* @api private
*/
// 返回promise返回错误信息,则抛出错误信息
function onRejected(err) {
var ret;
try {
// 抛出错误信息,利用了generator对象在外部抛出的错误可以被内部捕获
// 如果yield被try...catch包裹,就会被catch捕获,
ret = gen.throw(err);
} catch (e) {
// 如果内部的yield没有被try...catch, 则会在co().catch()中捕获
return reject(e);
}
// 继续遍历下一个yield
next(ret);
}

/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/

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) + '"'));
}
});
}

/**
* Convert a `yield`ed value into a promise.
*
* @param {Mixed} obj
* @return {Promise}
* @api private
*/
// 判断 yield 后面的数据类型,并尝试封装为promise
function toPromise(obj) {
// 如果不存在的话,直接返回,走最后的报错流程
if (!obj) return obj;
// 如果是promise,直接返回
if (isPromise(obj)) return obj;
// 如果是Generator函数或对象,则继续调用co执行
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// 如果是thunk函数,则转换为promise
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
// 如果是Array,则将Array中的元素封装为promise
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
// 如果是Object,则将Obj中的值封装为promise,执行并返回
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}

/**
* Convert a thunk to a promise.
*
* @param {Function}
* @return {Promise}
* @api private
*/
//
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
fn.call(ctx, function (err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}

/**
* Convert an array of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Array} obj
* @return {Promise}
* @api private
*/

function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}

/**
* Convert an object of "yieldables" to a promise.
* Uses `Promise![Alt text](./前端基础.md)
.all()` internally.
*
* @param {Object} obj
* @return {Promise}
* @api private
*/
//
function objectToPromise(obj){
// 构造一个Object对象
var results = new obj.constructor();
// 获取所有的key
var keys = Object.keys(obj);
var promises = [];
//将obj对象中的值全部转换为promise对象
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]);
//转换成功,则将值替换为promise对象,
//这里很巧妙,调用defer执行了promise对象,并将结果存储到了results中,并返回了一个promise对象存储到了promises数组中
if (promise && isPromise(promise)) defer(promise, key);
//转换失败,则原值不变
else results[key] = obj[key];
}

// 调用Promise.all并发执行刚刚生成的promises数组,并将结果返回
return Promise.all(promises).then(function () {
return results;
});

function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
//执行obj中使用promise包装后的value, 并返回一个promise对象存储到数组中,用于Promise.all调用
promises.push(promise.then(function (res) {
results[key] = res;
}));
}
}

/**
* Check if `obj` is a promise.
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/
// 判断是否是 promise对象
function isPromise(obj) {
return 'function' == typeof obj.then;
}

/**
* Check if `obj` is a generator.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/
// 判断是否是Generator对象
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}

/**
* Check if `obj` is a generator function.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/
// 判断是否是Generator函数
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}

/**
* Check for plain object.
*
* @param {Mixed} val
* @return {Boolean}
* @api private
*/
// 判断是否为原生的Object对象
function isObject(val) {
return Object == val.constructor;
}