【JavaScript】用 Generator 函数实现 async/await 语法

2023/01/31 17:19:29

async/awaitGenerator 函数的语法糖。

示例

const getData = () =>
  new Promise((resolve) => setTimeout(() => resolve("data"), 1000));

async function test() {
  const data = await getData();
  console.log("data: ", data);
  const data2 = await getData();
  console.log("data2: ", data2);
  return "success";
}

// 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
test().then((res) => console.log(res));

用 Generator 实现

对于上面示例,如果用 Generator 来实现,可以改写为以下形式:

function* testGen() {
  const data = yield getData();
  console.log("data: ", data);
  const data2 = yield getData();
  console.log("data2: ", data2);
  return "success";
}

Generator 函数需要调用 .next() 方法才能往下执行,所以需要封装一个自动执行函数:

const test = asyncToGenerator(testGen);

test().then((res) => console.log(res));

思考之后完成如下实现:

function asyncToGenerator(gen) {
  return new Promise((resolve, reject) => {
    const iterator = gen();
    (function exec(value, done = false) {
      const result = iterator.next(value);
      if (result.done) {
        return resolve(result.value);
      }
      return Promise.resolve(result.value).then(exec, reject);
    })();
  });
}
  • asyncToGenerator() 方法:
    • 接收一个 Generator 函数,返回一个执行器方法,这个方法会异步执行每一个 yield 语句,最终返回一个 Promise。
  • asyncToGenerator() 内部的 exec() 方法:
    • 接收 Generator 函数生成的迭代器,不断调用 .next() 方法直至完成所有 yield 任务。
    • exec() 方法在 done === true 后立即 resolve,不需要关注 Generator 函数最后的 return 值类型,因为最后的 return 值会由外部的 then 方法处理(test().then())。

完整例子

const getData = () => {
  return new Promise((resolve) =>
    setTimeout(() => resolve(Math.random()), 1000)
  );
};

function asyncToGenerator(gen) {
  return new Promise((resolve, reject) => {
    const iterator = gen();

    (function exec(value, done = false) {
      const result = iterator.next(value);
      if (result.done) {
        return resolve(result.value);
      }

      if (result.value instanceof Promise) {
        result.value.then(exec).catch(reject);
      } else {
        exec(result.value);
      }
    })();
  });
}

function* testGen() {
  const data = yield getData();
  console.log("data: ", data);
  const data2 = yield getData();
  console.log("data2: ", data2);
  return Promise.resolve(122);
}

const test = () => asyncToGenerator(testGen);

test().then(console.log).catch(console.log);

参考

手写 async await 的最简实现(20 行)open in new window

babel 如何编译 async 语法