源码浅析--co如何自动执行 Generator 函数

56 阅读3分钟

Generator

普通函数一旦开始执行,就会运行到最后或者遇到 return 结束,运行期间不会被打断。而 Generator 是 ES6 提供的一种异步编程解决方案,可以根据 yield 关键字一步一步执行。

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = generator(); // "Generator { }"

console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

co

可以自动执行 Generator 函数,并转换为 Promise。

const co = require("co");

function createPromise(res) {
  return Promise.resolve(res).then(r => r);
}

function* generator() {
  const a = yield createPromise(1);
  const b = yield createPromise(2);
  const c = yield createPromise(3);
  console.log(a, b, c); // 1 2 3
  return 123;
}

co(generator).then(res => {
  console.log(res); // 123
});

简单实现

因为 Generator 函数执行后,返回的是一个 Iterator 对象,那我们可以通过 while 循环执行去实现。

function getPromiseValue(promise) {
  return promise
}

async function co(gen) {
  gen = gen();
  let val;
  while (true) {
    let ret = await gen.next(val);
    val = await getPromiseValue(ret.value);
    if (ret.done) return val;
  }
}

源码分析

接收一个 Generator 函数,返回一个 Promise 对象。

function co(gen) {
  return new Promise((resolve, reject) => {})
}

作为工具库要对参数进行判断,判断 gen 是否为 Generator,如果是则执行该函数,如果不是,则结束 Promise 的执行。

function co(gen) {
  const ctx = this
  return new Promise((resolve, reject) => {
    if (typeof gen === 'function') gen = gen.call(ctx)
    if (!gen || typeof gen.next !== 'boolean') return resolve(gen)
  })
}

开始轮询 Generator 函数的内部指针对象,将其包装成 onFulilled 函数。

function co(gen) {
  const ctx = this;
  return new Promise((resolve, reject) => {
    if (typeof gen === "function") gen = gen.call(ctx);
    if (!gen || typeof gen.next !== "boolean") return resolve(gen);

    onFulfilled();
    /**
     * 开始执行下一次 yield 关键字对应的步骤
     * @param {*} res 上一步执行返回的值
     * @returns 
     */
    function onFulfilled(res) {
      var ret;
      try {
        // 获取 yield 下一步对应的执行内容
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      // 开始执行
      next(ret);
      return null;
    }
    /**
     * 执行 Generator 的每一步
     * @param {*} ret Gernerator 的单步骤
     * @returns 
     */
    function next(ret) {
      // ret.done 为 true 标识 Gererator 执行结束,返回最后一步的 value 值
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      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) + '"'));
    }
  });
}

最为重要的就是 next 函数内这段代码的执行,利用 next 函数和 onFulilled 函数构成一个类递归调用的过程,不断通过更改 Gernerator 函数内部指针对象,将每一次 Gererator 函数的 next 执行对象用 toPromise 函数包装成新的 Promise,onFulfilled 作为Promise 执行成功的函数。

if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

流程

image.png

总结(Promise、Generator、async对比)

Promise

Promise 是异步编程的一种解决方案,是为了解决回调函数产生的问题而诞生的,但是 Promise 无法通过 try catch 捕获错误,只可以通过 then的第二个回调或 catch 来捕获,并且 Promise 一旦新建就会立即执行,无法取消

Genertaor

Generator 是异步编程的解决方案,它不同于以往的函数一旦执行只有执行结束或者遇见 return 才会执行,而是一种可以暂停执行的函数,yield就是暂停的标志

Async

async 是依照 Genertaor 的使用方式对 Promise 做的使用扩展,是 Promise 的语法糖