本文只在我的博客和 SegmentFault 社区我的专栏发表,转载请注明出处
我的博客: https://zengxiaotao.github.io
SegmentFault 我的专栏: https://segmentfault.com/blog...node
学 nodejs 固然避免不了学习框架,毕竟原生的 API 较底层。最早接触的是 Koa 。看看官网的描述git
next generation web framework for node.jsgithub
我翻译一下就是: 基于 node.js 的下一代 web 开发框架。好像很厉害的样子!koa 是一个轻量级的框架,本质上提供了一个架子,经过 各类中间件的级联的方式实现特定的功能。koa 借助 promise 和 generator , 很好解决了异步组合问题。web
那什么又是 co 。学习 koa 就必定少不了学习 co 模块。co 模块能够将异步解放成同步。co 函数接受一个 generator 函数做为参数,在函数内部自动执行 yield 。segmentfault
使用的 co 模块版本号为 4.6.0数组
首先看一些用于判断对象类型的函数promise
var slice = Array.prototype.slice; // 对数组 slice 方法的引用
function isObject(val) { return Object == val.constructor; }
这两个应该就不用说了吧。。。app
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 方法。koa
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