本文只在个人博客和 SegmentFault 社区个人专栏发表,转载请注明出处
个人博客: https://zengxiaotao.github.io
SegmentFault 个人专栏: https://segmentfault.com/blog...
写在前面
学 nodejs 当然避免不了学习框架,毕竟原生的 API 较底层。最先接触的是 Koa 。看看官网的描述
next generation web framework for node.js
我翻译一下就是: 基于 node.js 的下一代 web 开发框架。好像很厉害的样子!koa 是一个轻量级的框架,本质上提供了一个架子,通过 各种中间件的级联的方式实现特定的功能。koa 借助 promise 和 generator , 很好解决了异步组合问题。
那什么又是 co 。学习 koa 就一定少不了学习 co 模块。co 模块可以将异步解放成同步。co 函数接受一个 generator 函数作为参数,在函数内部自动执行 yield 。
co 源码分析
使用的 co 模块版本号为 4.6.0
首先看一些用于判断对象类型的函数
var slice = Array.prototype.slice; // 对数组 slice 方法的引用
function isObject(val) {
return Object == val.constructor;
}
这两个应该就不用说了吧。。。
function isPromise(obj) {
return 'function' == typeof obj.then;
}
判断一个对象是否是一个 promise 实例,判断的依据也很简单,根据 “鸭子类型”,判断这个对象是否有 then 方法
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
类似的,判断一个对象时候是 generator 实例,只需判断这个对象是否具有 next 方法和 throw 方法。
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
判断是否是一个 generator 函数,只需判断这个函数是否是 GeneratorFunction
函数的实例
以上所讲的在之后将 value 包装成 promise 实例时都会用到。
看一下 co 模块的输出部分
module.exports = co['default'] = co.co = co
因此以下三种用法等价
var co = require('co') // (1)
var co = require('co').co // (2)
var co = require('co').default // (3)
接着就是重头戏 co
函数了。
function co(gen) {
var ctx = this; // 保存函数的执行上下文对象
var args = slice.call(arguments, 1) // 传给 gen 函数的参数
// 返回一个 promise 实例
return new Promise(function(resolve, reject) {
// 根据传入的 generator 函数生成一个 generator 实例
if (typeof gen === 'function') gen = gen.apply(ctx, args);
// 如果生成的 gen 不是一个 generator 实例,
// promise 直接变成 resolved 状态
if (!gen || typeof gen.next !== 'function') return resolve(gen);
// 执行 onFulfilled 方法
onFulfilled();
function onFulfilled(res) {
var ret;
try {
// 执行 gen 的 next 方法
ret = gen.next(res);
} catch (e) {
return reject(e);
}
// 并将这个值传入 next 函数
next(ret);
}
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
// 如果 gen 执行完毕, ret.done 变为 true ,那么这个 promise 的实例
// 的状态自然变成了 resolved
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value); // 将 value 重新包装成一个 promise 实例
// 新返回的 promise 实例的 resolve 方法设置为 onFulfilled 函数,再次执行 next 方法, 从而实现了自动调用 generator 实例的 next 方法
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 模块就实现了自动执行 generator 实例的 next 方法。那么接下来看看 co 是怎么把一个值转化为一个 promise 实例。
function toPromise(obj) {
if (!obj) return obj; // 如果传入的 obj 是假值,返回这个假值 如 undefined , false, null
if (isPromise(obj)) return obj; // 如果是 Promise 实例,返回这个 promise 实例
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 如果是 generator 函数或者 一个generator
if ('function' == typeof obj) return thunkToPromise.call(this, obj); // 如果是 thunk 函数
if (Array.isArray(obj)) return arrayToPromise.call(this, obj); // 如果是一个数组
if (isObject(obj)) return objectToPromise.call(this, obj); // 如果是一个 plain object
return obj; // 如果是原始值,则返回这个原始值。
}
那么每个函数依次看下去。
function thunkToPromise(fn) {
var ctx = this; // 保存函数上下文对象
// 返回一个 promise 实例
return new Promise(function (resolve, reject) {
// 执行传入的 thunk 函数
// thunk 函数接受一个 回调函数 作为参数
fn.call(ctx, function (err, res) {
// 如果 thunk 函数运行错误
// promise 实例的 变为 rejected 状态,执行 reject 函数,也就是 co 函数内定义的 onRejected 函数,下同
if (err) return reject(err);
// 获得多余参数
if (arguments.length > 2) res = slice.call(arguments, 1);
// promise 状态变为 resolved ,执行 resolve 函数,也就是 onFulfilled 函数
resolve(res);
});
});
}
所以,总结一下就是说,如果 generator 里 yield 后面是一个 thunk 函数, 这个 thunk 函数接受一个回调参数作为参数,co 在这个回调函数里定义了何时将 promise 的状态变为 resolved 或者 rejected ,
function arrayToPromise(obj) {
// Promise.all 方法返回一个 新的 promise 实例
// 如果 obj 是一个数组,把每个元素包装成一个 promise 实例
// 如果每一个 promise 如果都变为 resolved 状态
// 那么返回的新的 promise 实例的状态变为 resloved 状态
// 传给 resolve 函数的参数为之前每个 promise 的返回值所组成的数组
return Promise.all(obj.map(toPromise, this));
}
同样,如果 obj 是一个数组,也就是 yield 语句后面的表达式的值为一个数组,那么就执行 Promise.all 方法, 将数组的每一项都变成一个 promise 实例。
具体方法如下:
使用 toPromise 方法将 obj 数组中的每一项都包装成一个 promise 实例
如果上一步中的数组中有元素不是 promise 实例,Promise.all 方法将调用 Promise.resolve 方法,将其转化为 promise 实例。
Promise.all 方法返回一个新的 promise 实例。
只有 promise 实例数组中的所有实例的状态都变为 resolved 状态时,这个新的 promise 实例的状态才会变成 resolved。只要数组中有一个 promise 实例的状态变为 rejected ,新的promise 实例状态也马上变为 rejected 。
当返回的新的 promise 实例状态变为 resolved 时,传入其 resolve 函数的参数为之前数组中每个 promise 实例调用 resolve 函数的返回值组成的数组。如果返回的新的 promise 的状态变为 rejected ,那么传给 reject 函数的参数为数组中的 promise 实例最先变为 rejected 状态的那一个执行 reject 函数的返回值。
真绕口,多看几遍应该就能理解了。
最后来看看如果 ret.value 如果是一个对象,co 模块是怎么样把它变成一个 promise 实例的。
function objectToPromise(obj){
// 定义一个空对象
var results = new obj.constructor();
// 获取 obj 的全部属性
var keys = Object.keys(obj);
// 用于盛放 每个属性值生成的对应的 promise 实例
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]); // 根据属性值生成一个 promise 实例
if (promise && isPromise(promise)) defer(promise, key);
else results[key] = obj[key];
}
// 通过一个 promise.all 方法返回一个新的实例
return Promise.all(promises).then(function () {
return results; // 将 results 作为 onFulfilled 函数的参数
});
// 函数的作用
// 给 promise 添加 resolve 函数
// 并且把这个 promise 实例推入 promises 数组
function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
promises.push(promise.then(function (res) {
results[key] = res; // 定义promise 实例的 resolve 函数
}));
}
}
总结
分析完 co 的整个源码总结一下整个执行的过程。首先,co 函数接受一个 generator 函数,并且在 co 函数内部执行,生成一个 generator 实例。调用 generator 的 next 方法, 对生成的对象的 value 属性值使用 toPromise 方法,生成一个 promise 实例,当这个 promise 实例的状态变为 resolved 时,执行 onFulfilled 方法,再次对 generator 实例执行 next 方法,然后重复整个过程。如果出现错误,则执行这个 promise 实例定义的 reject 函数即 onRejected 方法。
以上即实现了将异步过程同步化。
最后欢迎 star
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。