手写Generator自执行器(基于Promise)

1,067 阅读4分钟

Generator是ES6推出的新的语法。Generator通过协程实现,具有执行时暂停并交出执行权,之后又从暂停处恢复执行的特性。这使得Generator可以用于处理异步逻辑。但Generator本身并没有自执行功能,所以通常会搭配类似于co这种执行器一起使用。

在Generator发布不久, async/await也随之发布,它相当于是自带执行器的Generator,且语法相对更语义化,所以比Generator更为常用。

本节就探究一下,不借助async/await,如何实现Generaor自执行器,使之能达到和async/await差不多的效果。

以下面代码为例:

function* foo() {
  const a = yield 1;
  const b = yield 2;
  const c = yield 3;
  return 4;
}

对于同步代码来说,要让其自动执行非常简单:

function run(fn) {
  const it = fn();
  let result = it.next();
  while (!result.done) {
    result = it.next(result.value);
  }
  return result.value;
}

const result = run(foo);
console.log(result); // 4

但这种简单的处理方式无法处理异步逻辑,为了处理异步逻辑,我们需要:

  1. 自执行函数需要返回一个Promise对象,用于获取Generator最终结果
  2. 每次调用next后,将返回的结果转换为Promise对象,并且在该Promise完成后,在其then方法中继续调用next方法直至Generator完成为止

按照这个思路,先写点便于理解的代码:

function mockFetch(data, timeout, fail) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // fail参数是为了模拟异步请求出错的情况
      if (fail) {
        reject("error");
      }

      resolve(data);
    }, timeout);
  });
}

function* bar() {
  const a = yield mockFetch("a", 100);
  const b = yield mockFetch("b", 100);
  const c = yield mockFetch("c", 100);
  return [a, b, c];
}

const it = bar();
const res1 = it.next();

res1.value.then((a) => {
  const res2 = it.next(a);
  res2.value.then((b) => {
    const res3 = it.next(b);
    res3.value.then((c) => {
      const res4 = it.next(c);
      console.log("result", res4); // result { value: [ 'a', 'b', 'c' ], done: true }
    });
  });
});

通过上述的代码,就已经实现了一个最基础的自执行器,但这种方法只适用于bar函数。

下面开始真正的实现满足需要的自执行函数。

首先创建一个run函数,它接受Generator函数和Generator函数的参数作为参数,并且返回一个Promise对象:

function run(fn, ...args) {
  return new Promise((resolve, reject) => {     
  });
}

在执行Generator函数之前需要做一些边界判断:

function run(fn, ...args) {
  return new Promise((resolve, reject) => {
    let it;
    // fn必须是一个函数
    if (typeof fn === "function") {
      it = fn(...args);
    }
    // fn返回的对象必须是一个迭代器
    if (!it || typeof it.next !== "function") {
      return resolve(it);
    }
  });
}	

接下来就是重头戏了:

function run(fn, ...args) {
  return new Promise((resolve, reject) => {
  	/* ...... */
    onResolved();

    function onResolved(value) {
      let res = it.next(value);
      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // 将结果转换为Promise,然后进行统一处理
      Promise.resolve(res.value).then(onResolved);
    }
  });
}

现在调用bar函数就变得相当简单了:

run(bar).then((data) => {
  console.log(data); // [1, 2, 3]
});

接下来进行异步操作的错误处理,在async/await中,当异步操作出错时,可以使用try/catch捕获并进行错误处理:

async function baz() {
  try {
    const a = await mockFetch("a", 100, true);
  } catch (error) {
    // 错误处理
  }
}

为了向Generator函数中传递Promise reject时的异常,需要用到throw方法:

function run(fn, ...args) {
  return new Promise((resolve, reject) => {
 		/* ...... */
    onResolved();

    function onResolved(value) {
      let res;
			// 如果Generator内未捕获异常,则会向外抛出,所以需要外部也进行异常处理
      try {
        res = it.next(value);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function onRejected(reason) {
      let res;

      try {
        // 将reject的reason传递给Generator函数处理
        res = it.throw(reason);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // 将结果转换为Promise,然后进行统一处理
      Promise.resolve(res.value).then(onResolved, onRejected);
    }
  });
}

写点代码测试一下:

// 内部未进行错误处理
function* foo() {
  const a = yield mockFetch("data", 1000, "error");
}

// 内部有进行错误处理
function* bar() {
  const a = yield mockFetch("data", 1000);
  let b;
  try {
    b = yield mockFetch("data", 1000, "error");
  } catch (error) {
    console.log("error happened");
  }
  return [a, b];
}

run(foo).catch((error) => {
  console.log("foo error", error); // foo error error
});

run(bar).then((data) => {
  console.log("bar data", data); // bar data ["data", undefined]
});

至此我们就完成了一个简化版的co,完整代码如下:

function run(fn, ...args) {
  return new Promise((resolve, reject) => {
    let it;
    // fn必须是一个函数
    if (typeof fn === "function") {
      it = fn(...args);
    }
    // fn返回的对象必须是一个迭代器
    if (!it || typeof it.next !== "function") {
      return resolve(it);
    }

    onResolved();

    function onResolved(value) {
      let res;

      try {
        res = it.next(value);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function onRejected(reason) {
      let res;

      try {
        res = it.throw(reason);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // 将结果转换为Promise,然后进行统一处理
      Promise.resolve(res.value).then(onResolved, onRejected);
    }
  });
}

如果对本文有什么意见和建议,欢迎讨论和指正!