Babel的奇妙冒险 @babel/plugin-transform-async-to-generator

1,117 阅读3分钟

预备知识

async/await 规则

  • await 必须出现在 async 声明的函数后面
  • await 后面的值如果是一个 Promise,await 将等待 Promise 正常处理完成并返回其处理结果
  • await 后面的值如果不是一个 Promise,await 会把该值转换为已正常处理的Promise,然后等待其处理结果
  • await 后面的 Promise 的状态 pending -> fulfilled 后, await 后面的代码会放在微任务队列中

Generator 函数和自动执行器表示 async/await

async函数文档中以后这样一句话:

The purpose of async/await is to simplify the syntax necessary to consume promise-based APIs. The behavior of async/await is similar to combining generators and promises.

async/await的目的为了简化使用基于promise的API时所需的语法。async/await的行为就好像搭配使用了生成器和promise

这里的“好像搭配使用了生成器和promise”怎么理解呢?我在网上找到了下面这段代码,很好的解释了这里的“搭配使用了生成器和promise”

async function fn(args){
  // await ...
  // await ...
  // await ...
}

// 等同于
function fn(args){
  function spawn(genF) {
    return new Promise(function(resolve, reject) {
        var gen = genF();
        function step(nextF) {
            try {
                var next = nextF();
            } catch(e) {
                return reject(e);
            }
            if(next.done) {
                return resolve(next.value);
            }
            Promise.resolve(next.value).then(function(v) {
                step(function() {
                    return gen.next(v);
                });
            }, function(e) {
                step(function() {
                    return gen.throw(e);
                });
            });
        }
        step(function() {
            return gen.next(undefined);
        });
    });
  }

  return spawn(function*() {
    // yield ...
    // yield ...
    // yield ...
  });
}
fn()

这段代码的思路是:将Generator 函数和自动执行器,包装在一个函数里。

  1. fn 函数返回一个新的函数 spawn 的执行结果;
  2. spawn 函数参数是一个 生成器函数
  3. spawn 返回一个 Promise, 对应与 async 函数返回一个 Promise;
let hello = async () => { return "Hello" };
hello().then((value) => console.log(value))
// output: Hello

let helloWorld = async () => {
    await "Hello"
    await "world"
    return "Nick"
};
helloWorld().then((value) => console.log(value))
// output: Nick
  1. spawn 内部创建了一个生成器 var gen = genF();
  2. spawn 会执行 step 函数, 参数是一个 function, nextF;
  • 首次执行, next(undefined), 表示不向生成器传值
  • 如果生成器没有执行结束, step 会把当前生成器产生的值, 即 yield 的返回值通过 Promise.resolve 封装为 Promise
    • 如果 yield 的返回值是Promise,返回原Promise
    • 如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态
    • 否则返回的promise将以此值完成
  • 当 Promise 为完成状态时,会把 yield 后面的代码放在微任务队列
  • 当执行到该微任务时, 再次执行next方法, 把上次产生的值放传递给生成器
  • 循环执行, 直到生成器结束

这样就用 Generator 函数和自动执行器产生了 async/await 的执行效果, 所以也可以说 async/await 是 Generator 的语法糖

源码解析

AST spec

interface Function <: Node {
  id: Identifier | null;
  params: [ Pattern ];
  body: BlockStatement;
  generator: boolean;
  async: boolean;
}

插件代码

export default declare((api, options) => {
  api.assertVersion(7);

  const { method, module } = options;

  if (method && module) {
    return {
      name: "transform-async-to-generator",

      visitor: {
        Function(path, state) {
          if (!path.node.async || path.node.generator) return;

          let wrapAsync = state.methodWrapper;
          if (wrapAsync) {
            wrapAsync = t.cloneNode(wrapAsync);
          } else {
            wrapAsync = state.methodWrapper = addNamed(path, method, module);
          }

          remapAsyncToGenerator(path, { wrapAsync });
        },
      },
    };
  }

  return {
    name: "transform-async-to-generator",

    visitor: {
      Function(path, state) {
        if (!path.node.async || path.node.generator) return;

        remapAsyncToGenerator(path, {
          wrapAsync: state.addHelper("asyncToGenerator"),
        });
      },
    },
  };
});

// remapAsyncToGenerator 部分代码
export default function (
  path: NodePath,
  helpers: { wrapAsync: Object, wrapAwait: Object },
) {
  path.traverse(awaitVisitor, {
    wrapAwait: helpers.wrapAwait,
  });

  const isIIFE = checkIsIIFE(path);

  path.node.async = false;
  path.node.generator = true;

  wrapFunction(path, t.cloneNode(helpers.wrapAsync));

  const isProperty =
    path.isObjectMethod() ||
    path.isClassMethod() ||
    path.parentPath.isObjectProperty() ||
    path.parentPath.isClassProperty();

  if (!isProperty && !isIIFE && path.isExpression()) {
    annotateAsPure(path);
  }
}

代码主要工作先通过 !path.node.async || path.node.generator 判断如果不是 async 声明的函数或者是 generator 函数则返回. 否则调用 babel-helper-remap-async-to-generator 将 async 转为 generator, 如果是立即执行函数, 会有额外的处理.

UT

// input
(async function() { await 'ok' })();
(async function notIIFE() { await 'ok' });

// output
babelHelpers.asyncToGenerator(function* () {
  yield 'ok';
})();

/*#__PURE__*/
(function () {
  var _notIIFE = babelHelpers.asyncToGenerator(function* () {
    yield 'ok';
  });

  function notIIFE() {
    return _notIIFE.apply(this, arguments);
  }

  return notIIFE;
})();

从 UT 可以看出 async/await 被转成了 babelHelpers.asyncToGenerator 再看一下 asyncToGenerator 长什么样子:

helpers.asyncToGenerator = helper("7.0.0-beta.0")`
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
    try {
      var info = gen[key](arg);
      var value = info.value;
    } catch (error) {
      reject(error);
      return;
    }

    if (info.done) {
      resolve(value);
    } else {
      Promise.resolve(value).then(_next, _throw);
    }
  }

  export default function _asyncToGenerator(fn) {
    return function () {
      var self = this, args = arguments;
      return new Promise(function (resolve, reject) {
        var gen = fn.apply(self, args);
        function _next(value) {
          asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
        }
        function _throw(err) {
          asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
        }

        _next(undefined);
      });
    };
  }
`;

和上面 Generator 函数和自动执行器表示 async/await的代码如出一辙.

小结

async/await 的目的为了简化使用基于promise的API时所需的语法, 可以说是 Generator 的语法糖。 babel 通过 plugin-transform-async-to-generator 将 async/await 转为 Generator和自动执行器的方式。