CO源码解读

452 阅读2分钟

Co是什么?

co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。

其实就是Generator函数的语法糖,现在我们的的async/await 函数和co就很类似,不需要我们再每次都使用next才能走下一步,也就可以自动运行Generator函数。

准备环境

因为代码不多,所以我使用的是yarn下载的包进行学习

// 自动生成package.json文件 
yarn init -y yarn add co

主要函数

主要流程就是递归执行next达到自动执行的效果

function co(gen) {
  var ctx = this;// 获取上下文this
  var args = slice.call(arguments, 1)// 取传入函数后面的参数

  return new Promise(function(resolve, reject) {
    // 如果gen是函数,先执行Generator函数
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 判断gen是否函数,不是就直接返回
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);// 把下一步yield的值传进next
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */
    // reject抛出异常函数
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */

    function next(ret) {
      if (ret.done) return resolve(ret.value);// done为true就结束直接返回
      // 否则把ret.value的返回值转换成promise
      var value = toPromise.call(ctx, ret.value);
      // 转换成功后调用then函数继续执行onFulfilled、和onRejected方法
      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) + '"'));
    }
  });
}

toPromise

把yield返回值转为promise

function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}

// 转换函数为promise
function thunkToPromise(fn) {
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}

// Promise.all数组转换为promise
function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}

// 转换对象为promise对象
function objectToPromise(obj){
  var results = new obj.constructor();
  var keys = Object.keys(obj);
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var promise = toPromise.call(this, obj[key]);
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
  return Promise.all(promises).then(function () {
    return results;
  });

  function defer(promise, key) {
    // predefine the key in the result
    results[key] = undefined;
    promises.push(promise.then(function (res) {
      results[key] = res;
    }));
  }
}

异步并发

// 数组的写法
co(function* () {
  var res = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  console.log(res); 
}).catch(onerror);

// 对象的写法
co(function* () {
  var res = yield {
    1: Promise.resolve(1),
    2: Promise.resolve(2),
  };
  console.log(res); 
}).catch(onerror);

总结

看懂了co的知识点,再也不怕面试官手撕async代码了,嘿嘿。

参考文章

[1]阮一峰-co 函数库的含义和用法