zoukankan      html  css  js  c++  java
  • Generator函数执行器-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

        

            

  • 相关阅读:
    Docker用途 & 和tomcat的区别
    Ubuntu安装Redis
    Ubuntu查看和设置Root账户
    Oracle常用语句
    Redis知识总结
    Blazor学习笔记01: 使用BootstrapBlazor组件 创建一个具有单表维护功能的表格页面
    NET Core之积沙成塔01: 解决Visual Studio 2019 代码提示为英文
    MySQL系统自带的数据库information schema
    Windows安装mysql方法
    数据库之概念
  • 原文地址:https://www.cnblogs.com/pqjwyn/p/7350820.html
Copyright © 2011-2022 走看看