co函数库源码解析
Posted 潇湘待雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了co函数库源码解析相关的知识,希望对你有一定的参考价值。
一、co函数是什么
co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。短小精悍只有短短200余行,就可以免去手动编写Generator 函数执行器的麻烦
二、co函数怎么用
举个栗子就能清楚的知道如何使用co函数
1 function* gen(){ 2 var f1 = yield func1; 3 var f2 = yield fnuc2; 4 //sth to do 5 };
手动执行和co函数执行的写法如下
1 // 手动执行 2 var wp = gen() 3 gen.next() 4 gen.next() 5 // co 函数 6 var co = require(\'co\') 7 co(gen)
两者的差别应该一眼就能看出来了。
三、co函数如何实现自动执行
co用promise的特性,将整个Generator函数包装在一个promise下,利用Generator的next链,循环调用co方法将不同的next任务分别包装为不同的子promise。根据next的状态来执行不同的resolve,进而实现自动执行。
基本流程如下图,忽略我渣渣的画图能力
具体如何实现,下面一起看下源码
四、co源码解析
为了更好的分析,还是对源码的方法进行分类主要有以下两类:
4.1 辅助函数
辅助函数很好理解了,主要是用来做类型判断,参数解析等功能的。直接看代码
1 /** 2 * obj 是否promise 3 * 利用promise.then存在且为function 4 */ 5 6 function isPromise(obj) { 7 return \'function\' == typeof obj.then; 8 } 9 10 /** 11 * obj是否Generator 12 * 利用Generator的next 和 throw 两属性为Fuction的特点加以判断 13 */ 14 15 function isGenerator(obj) { 16 return \'function\' == typeof obj.next && \'function\' == typeof obj.throw; 17 } 18 19 /** 20 * 是否Generator方法 21 * 利用constructor的name和displayName属性。 22 * @example 23 * var a = {} 24 * a.constructor === Object 25 * a.constructor.name // "Object" 26 */ 27 28 function isGeneratorFunction(obj) { 29 var constructor = obj.constructor; 30 if (!constructor) return false; 31 if (\'GeneratorFunction\' === constructor.name || \'GeneratorFunction\' === constructor.displayName) return true; 32 return isGenerator(constructor.prototype); 33 } 34 35 /** 36 * 判断是否干净对象 37 * 利用constructor 属性。 38 * @example 39 * Object.constructor === Object 40 */ 41 42 function isObject(val) { 43 return Object == val.constructor; 44 }
4.2 功能函数,这里就按调用流程看
co:入口函数,将传入的函数先做类型判断然后返回一个promise对象
/** * 执行generator,返回一个promise对象 * 首次调用即将整个fn包在主promise中 */ function co(gen) { // 当前执行环境上下文 var ctx = this; // 获取参数 var args = slice.call(arguments, 1); return new Promise(function(resolve, reject) { /** * 生成一个gen实例 * 如果不是gen函数,结束并执行resolve回调 */ if (typeof gen === \'function\') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== \'function\') return resolve(gen); //.... }); }
这里首先生成一个gen函数实例,如果gen非generater函数,直接执行resolve
然后调用onFulfilled函数,即先执行一次generater.next,将value作为后面的执行的参数
1 function onFulfilled(res) { 2 var ret; 3 try { 4 /** 5 * 执行next,获取执行结果 6 */ 7 ret = gen.next(res); 8 } catch (e) { 9 return reject(e); 10 } 11 // 调用实现自动执行的关键函数,next 12 next(ret); 13 return null; 14 }
然后就到了实现自动执行的关键函数next
题外话,既然是实现了自动调用,无非是递归和迭代来调用执行函数,next函数就负责该部分内容
1 /** 2 * next函数的实现 3 * 一句话总结:如果为done,则value传入resolve并执行,否则调用co生成子promise,继续执行 4 */ 5 function next(ret) { 6 // done return 并执行reslove,即回到上层promise如果为主promise,则执行完成 7 if (ret.done) return resolve(ret.value); 8 // 执行结果创建子promise,不同数据结构实现方式不同 9 var value = toPromise.call(ctx, ret.value); 10 // 将onFulfilled作为resolve传入,确保子promise执行完成之后回到主promise。 11 // 这样next执行链创建完成 12 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 13 return onRejected(new TypeError(\'You may only yield a function, promise, generator, array, or object, \' 14 + \'but the following object was passed: "\' + String(ret.value) + \'"\')); 15 }
看到这里可能会问,具体的循环调用是在哪里实现的,不要急咱们再看下toPromise的实现:
1 /** 2 * 将obj转换成promise 3 * obj无非为以下几种类型: 4 * 1、非object的基本数据类型===>直接返回 5 * 2、promise===>直接返回 6 * 3、Generator对象和方法===> co调用 7 * 4、thunk函数===>thunkToPromise 8 * 5、Object ===>objectToPromise 9 */ 10 11 function toPromise(obj) { 12 if (!obj) return obj; 13 if (isPromise(obj)) return obj; 14 // 主要看这里,能转化为generator函数的最终都要再次调用co函数,生成子promise,这样就完成了循环调用 15 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); 16 if (\'function\' == typeof obj) return thunkToPromise.call(this, obj); 17 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); 18 if (isObject(obj)) return objectToPromise.call(this, obj); 19 return obj; 20 }
这里就是将各种类型转化为对应的promise,然后执行。到此co的整体流程就结束了。其他的如何实现这里就不做讲述了,完整的源码解析请移步co函数库源码解析查看。
上面就是我研究co源码之后的一些个人体会,希望能对其他人有所帮助。
参考文章:http://es6.ruanyifeng.com/#docs/generator
以上是关于co函数库源码解析的主要内容,如果未能解决你的问题,请参考以下文章