co源码小析
作为koa的内核,co的代码短小精悍,可以来简单的欣赏一下(PS:我计划做一些小库的源码解析,欢迎关注和提出意见)。
这里我基于的版本是4.5.2,从4.0.0开始co
返回的就是Promise了。
首先我们来看一下源码目录,比较简单,我们用tree
命令看一下,文件的说明也写在注释里了:
➜ co git:(master) tree -LaA 2
.
├── .gitignore # .git忽略文件
├── .travis.yml # travis持续集成配置文件
├── History.md # 历史纪录
├── LICENSE # 许可
├── Readme.md # 简介文档
├── component.json # component前端组件管理工具的描述文件
├── index.js # 主文件
├── package.json # npm包描述文件
└── test # 测试目录
├── arrays.js # 测试yield []的情况
├── context.js # 测试传递上下文是否正确
├── generator-functions.js # 测试支持生成器函数及生成器函数抛错
├── generators.js # 同上,一样一样的
├── invalid.js # 不支持的情况抛错
├── objects.js # 测试yield后为对象时的情况
├── promises.js # 测试yield后为promises的情况
├── recursion.js # 测试数组嵌套和对象嵌套的情况
├── thunks.js # yield thunks的情况
└── wrap.js # 测试co.wrap函数
7 directories, 25 files
先搞周边,再搞index.js
文件,更多的东西可以清楚一点,也可以学到更多的东西,按上面的文件顺序来
.gitignore
过滤了node_modules
和coverage
目录,node_modules
是npm安装时包默认目录,coverage
是执行npm run test-cov
生成的目录,这个下面再细讲
.travis.yml
指定了运行环境和持续集成的脚本
几个文档
History.md
和LICENSE
就不用多说了, 如果使用co
,Readme.md
看看就可以用了
package.json
包描述文件,注意engines
字段,co
需要在iojs
1.0.0及以上版本,node
0.12.0及以上版本运行。
scripts字段里有三个命令
- test : 测试,建议使用nvm安装iojs,然后
npm run test
或者npm test
查看执行效果 - test-cov : 测试覆盖率
- test-travis : 在travis上使用的命令
test目录
如果看更详细的使用,推荐看test目录里的文件,:)
index.js
直接给注释了放下面了
/**
*快捷方式,暂存一下数组的slice方法
*/
var slice = Array.prototype.slice;
/**
* 导出co,co['default']和co.co不知道为什么要这么干,为了兼容老版本?
*/
module.exports = co['default'] = co.co = co;
/**
* 把一个generator函数变成返回Promise的函数
*/
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
/**
* 执行一下生成器函数或者生成器返回promise
*/
function co(gen) {
//保存上下文
var ctx = this;
//把所有的东西都包装成一个Promise
return new Promise(function(resolve, reject) {
//传入的如果是生成器函数,运行它使用它的生成器
if (typeof gen === 'function') gen = gen.call(ctx);
//传入的是空值,或者不是生成器,直接返回传入值
if (!gen || typeof gen.next !== 'function') return resolve(gen);
//执行生成器的next方法
onFulfilled();
/**
* 把res当生成器的入参执行next
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* promise出错时的回调
*/
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);
//尝试将yield返回的值包装成promise
var value = toPromise.call(ctx, ret.value);
//value有值且为promise,继续执行
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) + '"'));
}
});
}
/**
* Convert a `yield`ed value into a promise.
* 把一个yield之后的值转为promise
*/
function toPromise(obj) {
//传入对象为空值返回它
if (!obj) return obj;
//如果是传入promise,直接返回它自己
if (isPromise(obj)) return obj;
//是生成器函数或者生成器,调用co执行
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
//是一个函数,那么假定它是thunk,把它转为promise
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
//数组,把数组转换为promise
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
//对象转为promise
if (isObject(obj)) return objectToPromise.call(this, obj);
//跳过了上面的所有分支,返回obj自身
return obj;
}
/**
* 把thunk形式的函数转成promise
*
* thunk是单参数的函数,且参数为回调函数
* function (cb){
* something(arg1, arg2, cb)
* }
*/
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
//执行thunk函数
fn.call(ctx, function (err, res) {
if (err) return reject(err);
//参数大于2时,除去第一个参数,返回后面的所有参数组成的一个数组
if (arguments.length > 2) res = slice.call(arguments, 1);
//promise返回
resolve(res);
});
});
}
/**
* 把一个数组转成promise,这里用了Promise.all函数
*/
function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}
/**
* 把一个对象转成promise,它其实用的还是上面的数组转换的方式
*/
function objectToPromise(obj){
//生成一个结果对象
var results = new obj.constructor();
//保存和取对象的键数组
var keys = Object.keys(obj);
//定义一个数组,用来存生成对象值生成的promise
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
//把对象值转为promise
var promise = toPromise.call(this, obj[key]);
//有值且成功转成了promise,
if (promise && isPromise(promise)) defer(promise, key);
//直接放到结果对象
else results[key] = obj[key];
}
//调用Promise.all运行所有的promise,成功后返回结果
return Promise.all(promises).then(function () {
return results;
});
function defer(promise, key) {
// 预设results的值,这一步像是多余,对象属性默认就是undefined
// 这里是为了保证结果对象肯定有这个key
//results[key] = undefined;
// promise放到上面的数组里,Promise.all的时候统一执行
promises.push(promise.then(function (res) {
results[key] = res;
}));
}
}
/**
* 判断obj是不是promise,这里只是简单的判断对象上的then属性是否是函数
*/
function isPromise(obj) {
return 'function' == typeof obj.then;
}
/**
* 判断对象是否是生成器,这里判断对象上的next和throw是不是函数
*/
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
/**
* 判断obj是否是生成器函数,判断有没有构造器,有的话,它的构造器名字是不是"Generatorfunction"
* 判断构造器的原型是否是生成器
*/
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
/**
* 判断val是不是一个纯对象,用val的构造器来判断
*/
function isObject(val) {
return Object == val.constructor;
}