co-源码详细解读( async await 简单实现 )

939 阅读3分钟

什么是co?

co是用于 Generator 函数的自动执行的一个库,可以理解为一个语法糖,是程序员TJ Holowaychuk 于2013年6月发布的一个小工具。源代码地址

Generator函数是 ES6 提供的一种异步编程解决方案,可以用来控制函数的执行。通过 yield 关键字控制函数的状态以及 next()来执行。

co函数和async await很像,其实就像是个 Generator语法糖,不需要我们主动去yield/next下一步,自动执行。

主函数

co函数入参为Generator函数,返回结果为Promise

function co(gen) {
  // 获取上下文this
  var ctx = this;
  // 获取传入函数后所有参数
  var args = slice.call(arguments, 1);
	// 返回 promise
  return new Promise(function(resolve, reject) {
    // 如果gen是函数,先执行generator函数
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 如果gen有值 或 gen非迭代器对象 直接resolve
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
	  /**
    * 到这里的时候 gen结构为 
    * '{ value: xxx, next: function() {},done: true or false}'
    */ 
    
    // 这里 先去走第一个yield,因为还没走到gen.next() 所以不需要传参数
    onFulfilled();
		
    
    function onFulfilled(res) {
      var ret;
      try {
        // 调用gen.next传参数res,得到yield的结果
        ret = gen.next(res);
      } catch (e) {
        // 出错直接返回
        return reject(e);
      }
      next(ret); // 接受gen.next返回结果 去走判断逻辑
      return null;
    }
		// gen 执行throw方法,出错直接 reject
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e); 
      }
      next(ret);
    }
		 // 这里return 只是为了结束函数执行 return结果没有实际意义、切勿混淆
    function next(ret) {
      // 如果走完了,此时generator执行完毕 返回 resolve
      if (ret.done) return resolve(ret.value);
      // done为false,说明没走完。 把ret.value转成promise
      var value = toPromise.call(ctx, ret.value);
      // 如果转换完结果为promise,那么继续链式onFulfilled, onRejected 操作
      // 这里就形成了next递归
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // 如果转换的结果不是promise 那么抛出错误
      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

function toPromise(obj) {
  // 假值 直接返回
  if (!obj) return obj;
  // 为promise 直接返回
  if (isPromise(obj)) return obj;
  // 是generator生成器函数 或 generator迭代器对象 调用co返回
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // 为函数 转换为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);
  // 返回
  return obj;
}

thunkPromise

thunk方式去将函数转化为promiseThunk函数的含义和用法

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);
    });
  });
}

arrayToPromise

将数组的每一项转化为promise调用Promise.all返回。

function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}

objectToPromise

将对象的每一项转化为promise调用Promise.all返回。

function objectToPromise(obj){
  // 初始化 result
  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 则调用defer
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
  // 最后全部一起返回
  return Promise.all(promises).then(function () {
    return results;
  });
	// 将promise resolve结果塞到数组里
  function defer(promise, key) {
    // 先赋值为undefined
    results[key] = undefined;
    // 塞到promises数组里,并且把promise resolve的结果存到result里
    promises.push(promise.then(function (res) {
      results[key] = res;
    }));
  }
}

isPromise、isGenerator、isGeneratorFunction、isObject

// 判断是否为promise
function isPromise(obj) {
  return 'function' == typeof obj.then;
}
// 判断是否为generator迭代器对象
function isGenerator(obj) {
  return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
// 判断是否为generator生成器函数
function isGeneratorFunction(obj) {
  var constructor = obj.constructor;
  if (!constructor) return false;
  if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
  return isGenerator(constructor.prototype);
}
// 判断是否为对象
function isObject(val) {
  return Object == val.constructor;
}

co.wrap 函数

Generator 函数转换成 promise 函数。可重复使用,类似于缓存函数功能。

借助于高阶函数的特性,返回一个新函数createPromise,然后传给它的参数都会被导入到Generator函数中。

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};

// 例子🌰

// 查中文名1
co(function* () {
  const chineseName = yield searchChineseName('tom')
  return chineseName
})
// 查中文名2
co(function* () {
  const chineseName = yield searchChineseName('jarry')
  return chineseName
})
// 无法复用,通过co.wrap实现重复利用
const getChineseName = co.wrap(function* (name) {
  const filename = yield searchChineseName(name)
  return filename
})
getChineseName('tom').then(res => {})
getChineseName('jarry').then(res => {})

co函数导出方式

module.exports = co['default'] = co.co = co;

co 函数多种的方式的导出,那么也就有多种引入方式了。

const co = require('co')
require('co').co
import co from 'co'
import * as co from 'co'
import { co } form 'co'

async await简单实现

由此可见,async await也不过也是一个generator语法糖,就是像co函数一样,如果上面都看懂了的话,下面的代码也不就不难理解了。

function asyncToGenerator(generatorFunc) {
    return function() {
      const gen = generatorFunc.apply(this, arguments)
      return new Promise((resolve, reject) => {
        function step(key, arg) {
          let generatorResult
          try {
            generatorResult = gen[key](arg)
          } catch (error) {
            return reject(error)
          }
          const { value, done } = generatorResult
          if (done) {
            return resolve(value)
          } else {
            return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
          }
        }
        step("next")
      })
    }
}

简单总结

通过co的源码的学习,对generaitor迭代器生成器async await有了更深的理解,之后如果面试再遇到相关手写自执行generator,应该不成问题了。

写在最后

    我是思唯,一个开始尝试向社区输出的东西的前端开发一枚。

    文中如有错误,欢迎大家在评论区指正,如果这篇文章帮助到了你,欢迎点赞👍和关注❤️