co模块源码分析

2021-08-04 22:36:22 Javascript 大约 5 分钟

co模块是generator函数的自动执行函数,它基于 ES6 的 generator 和 yield ,让我们能用同步的形式编写异步代码。

# 示例

我们知道调用生成器函数并不会实际执行函数体,而是返回一个生成器(迭代器)对象,然而co模块改变了这一现状;

function* generator1() {
    const a = yield Promise.resolve(1);
    const b = yield Promise.resolve(2);
    console.log(a, '---', b);
}

const it = generator1();
console.log(it);
it.next().value.then(res => {
    console.log(res);
})
it.next().value.then(res => {
    console.log(res);
})
console.log(it.next());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上面函数的调用,你会发现第8行打印出来的是Generator对象,然而要想拿到两个promise的值,就必须执行it.next()方法并.then。而且函数中的console要想打印,就必须执行第15行的next()方法,可见利用Generator函数虽然可以让我们已同步的方式编写异步代码,但是我们却做了许多操作;

提示

it.next()如果不传值那么yield的值为undefined

优化:使用co模块 (opens new window),下载index.js文件并引入;

co(function* () {
    const a = yield Promise.resolve(1);
    const b = yield Promise.resolve(2);
    console.log(a, '---', b); // 1 --- 2
})
1
2
3
4
5

使用了co模块后你不需要在next便可以直接拿到值,这一步骤co帮我们做了;有没有感觉方便多了!

提示

async/await其实是Generator函数的语法糖;

# 原理

为什么 co 可以自动执行 Generator函数?我们知道Generator 就是一个异步操作的容器,它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点:

  • 回调函数:将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
  • Promise:将异步操作包装成 Promise 对象,用then方法交回执行权。

co模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成了一个模块。使用 co 的前提条件是,Generator函数的yield命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co。

# 源码下载

github下载co模块源码;传送门 (opens new window)。或者找到index.js把里面的代码拷贝下来;

# 源码分析

在源码中我们先找到co模块的入口:

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);

  // co 模块返回 Promise
  return new Promise(function(resolve, reject) {
    // 这两句是判断gen是否为 Generator 函数,如果是就执行 onFulfilled ,如果不是就 resolve(gen);
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    // 是 Generator函数调用 onFulfilled();
    onFulfilled();

    function onFulfilled(res) {
      var ret;
      try {
        // 因为在 gen.apply()时已经执行gen并返回了生成器对象,所以这里直接执行gen.next()方法;
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      // 实现自动执行的关键代码就是这个,它执行了上一次返回的生成器对象;
      next(ret);
      return null;
    }

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    function next(ret) {
      // 判断 done 是否为true,如果为true则说明 generator函数执行完毕(没有需要执行的 yield语句了)
      // 返回最后执行的结果 resolve(ret.value);
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      // 判断 value 是否为 promise,如果是就执行 then()方法,然后会调用 onFulfilled !!!
      // 重点 在这里就形成了一个递归调用 会一直执行下去直到yield语句执行完毕;
      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) + '"'));
    }
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

提示

上面的代码中的核心部分就是onFulfillednext这两个函数,产生了递归调用,从而实现了Generator函数的自动执行;

# co中其他方法介绍

  1. 把非Promise的值转换为Promise:

    // 这里只转化 Object、Generator、Array、Function,其他类型直接返回不做处理;
    // 所以next函数中会有这样的判断 if (value && isPromise(value)) 
    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;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    • Promise判断:

      // 此方法主要用于判断 一个变量是否是 Promise,
      function isPromise(obj) {
        // 只有 Promise 有 then方法;
        return 'function' == typeof obj.then;
      }
      
      1
      2
      3
      4
      5
    • isGeneratorFunction判断:

      // 这一步主要是判断 obj 是否为 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);
      }
      
      1
      2
      3
      4
      5
      6
      7
    • isGenerator判断:

      // 这一个和isGeneratorFunction是有区别的,这个判断的是 Generator 函数执行后返回的生成器对象;
      function isGenerator(obj) {
        return 'function' == typeof obj.next && 'function' == typeof obj.throw;
      }
      
      1
      2
      3
      4
    • isObject判断:

      function isObject(val) {
        return Object == val.constructor;
      }
      
      1
      2
      3
    • arrayToPromise:数组转promise:

      function arrayToPromise(obj) {
        return Promise.all(obj.map(toPromise, this));
      }
      
      1
      2
      3
    • thunkToPromise判断

      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);
          });
        });
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
    • objectToPromise判断

      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;
          }));
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

实现自动执行器的原理简单的说就是 next方法结合Promise

上次编辑于: 2023年7月4日 09:36