co源码小析


作为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_modulescoverage目录,node_modules是npm安装时包默认目录,coverage是执行npm run test-cov生成的目录,这个下面再细讲

.travis.yml

指定了运行环境和持续集成的脚本

几个文档

History.mdLICENSE就不用多说了, 如果使用coReadme.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;
}

推荐文章