IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    co源码小析

    草依山发表于 2015-05-29 15:40:00
    love 0

    作为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及以上版本,node0.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;
    }
    

    文章来源: co源码小析
    文章的标签: nodejs koa 源码小析


沪ICP备19023445号-2号
友情链接