阅读 523

CO源码解读

前言

最近通过公众号偶然得知了一个神秘组织,在那个神秘组织里每周都会进行一个大约两百行代码左右的优秀框架或者是某些大型框架内的部分源码的解读分析。在我加入这个组织的时候已经进入到第四期CO源码解读,当我看到这个CO的是时候也是一头雾水。原因有以下两点:

  • 一个五年前的库,在目前的前端开发中并不常见
  • 这个库作用于genertor函数,但在genertor在日常的开发中很难使用到,一般都是使用promise和 async等方式

但是经过自己的学习和结合一些面试过程中面试官的问题,我才明白了CO的作用。

co是用来做什么呢

image.png

解释这个问题之前我来问大家一个问题,一直都说async是一个generator的一个语法糖。如上图,但是为什么generator在运行的过程中的每一步(yield)是需要通过gen.next去完成的,而async不需要, 原因就是CO,CO是一个genrator的自动执行器,通过递归自动处理过程中的每一步yield,遇到错误就报错终止。

promise、generator和await的关系

上面其实说明了CO的作用,关于这里我其实就像用一个等式来表达: generator +(co)+ promise = async/await. 所以说CO其实这个东西吧,平时都有用到。但是由于js把一些底层的东西都封装好了,才让我们感受不到CO的存在。

用法示例

function request(name, ms = 1000) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({name});
    }, ms);
  });
}

function* getData() {
    let name1 = yield request('cc')
    let name2 = yield request(name1)
    return name2
}

// 按照正常的方式的用法

let data = getData()
let {value} = data.next()
value.then(res => {
    let { value } = data.next(res)
    value.then(res => {
        ...
    })
})

使用co的情况
co(getData()).then(res => {})
复制代码

以上用法会不会觉得使用CO方便了很多。本来无穷的promise只用一行代码就处理了。

co源码解析

代码虽然不多就200行左右,但是大部分代码是一些类型的判断,和一些数据转换的过程,由此我截取了其中最核心的一部门代码进行解析

function co(gen) {

  var ctx = this; // 保存当前环境下的this
  var args = slice.call(arguments, 1); // 保存除了gen的所有变量
  
  return new Promise(function(resolve, reject) {
    // 如果gen传入的是一个方法 则调用gen函数并保存返回值
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    //如果不存在返回值或者返回值的next不是一个方法 则直接返回gen
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
    // 先执行一下成功回调
    onFulfilled();
    // 成功的回调
    function onFulfilled(res) {
      var ret; // 用来保存gen.next的返回值
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e); // 如果失败直接报错
      }
      next(ret); // //把返回结果传入到next函数中
      return null;
    }

     // 失败回调 原理同上
    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); // 如果返回值已经是gen最后的节点了、则直接resolve(ret.value)
      var value = toPromise.call(ctx, ret.value); //否则就把ret.value的返回值转换成promise
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 转换成功后调用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) + '"')); //如果返回值类型不对则直接报错
    }
  });
复制代码

以上就是co的核心执行逻辑,主要是通过在两个回调函数中执行next,在next中通过value.then的方式在执行两个成功回调,形成一个闭环。一步一步的自动执行所有的gen.next,在ret的done为空的情况下跳出函数。

结语

以上就是我这次参加神秘组织(若川的源码解读)所学习到东西,以后我会周定期更新每一次的源码解读过程中所学习到的知识点,作为一个前端人,虽然JS的一些语法糖给了我们很大的便利,但是他也在一定程度上剥夺了一些我们对知识的了解,所以希望大家能有所收获,至少在面试的时候碰到关于async为什么不需要.next的时候能硬气的回答面试官因为CO。

附上一个神秘组织链接 www.yuque.com/ruochuan12

co框架的源码地址 github.com/tj/co

祝大家学有所成!!

image.png

文章分类
前端
文章标签