某次面试场景:
面试官:你知道 async/await
吗?
我:有所了解(心中窃喜,看来下面要问我事件循环方面的东西了,马上给你倒着背出来,稳得很)
面试官:那请你说下 Bable
是如何处理 async/await
的? 或者直接描述一下相关 polyfill
的原理
我:。。。(怎么不按套路出牌?)
我确实不知道这个东西,但为了避免尴尬,我只能秉持着虽然我不知道你说的这个东西但气势不能弱了一定要把你唬住的心理战术,利用自己所知道的东西,进行现场算命推测,声情并茂地介绍了一波 异步函数队列化执行的模式,然而遗憾的是,我虽然说得吐沫横飞,但终究没猜对
最近闲着没事,于是抽时间看了一下
polyfill 后的代码
既然想知道其原理,那么自然是要看下 polyfill
后的代码的,直接到 Babel官网的REPL在线编辑器上,配置好 presets
和 plugins
后,输入你想要转化的代码,babel
自动就会给你输出转化后的代码了
以下述代码为例:
async function test1 () {
console.log(111)
await a()
console.log(222)
await b()
console.log(3)
}
babel
输出的代码是:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
function test1() {
return _test.apply(this, arguments);
}
function _test() {
_test = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee() {
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log(111);
_context.next = 3;
return a();
case 3:
console.log(222);
_context.next = 6;
return b();
case 6:
console.log(3);
case 7:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _test.apply(this, arguments);
}
很明显,_test
函数中 while(1)
方法体的内容,是需要首先注意的代码
可以看出来,babel
把原代码进行了一次分割,按照 await
为界限,将 async
函数中的代码分割到了 switch
的每个 case
中(为了表述方便,下文将此 case
代码块中的内容称作 await
代码块),
switch
的条件是 _context.prev = _context.next
,与 _context.next
紧密相关,而 _context.next
这个变量,会在每个非 case end
中被赋值,值就是原代码中被分割后的下一个将要执行的 await
代码块的内容,当原代码中的所有 await
被执行完毕后,会进入 case end
逻辑,执行 return _context.stop()
,代表 async
函数已经执行完毕
但这只是最基本的,代码到底是怎么串连起来的,还要继续往外看
下文讲解的源代码版本:"@babel/runtime": “^7.8.4”
流程串连
首先,需要看下 _interopRequireDefault
这个方法:
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
"default": obj
};
}
module.exports = _interopRequireDefault;
代码很简单,如果参数 obj
上存在 __esModule
这个属性,则直接返回 obj
,否则返回一个属性 default
为 obj
的对象,其实这个主要就是为了兼容 ESModule
和 CommonJS
这两种导入导出规范,保证当前的引用一定存在一个 default
属性,否则没有则为其加一个 default
属性,这样便不会出现模块的 default
为 undefined
的情况了,就是一个简单的工具方法
然后继续看 _regenerator
,while(1)
这个循环体所在的函数,作为 _regenerator.default.wrap
方法的参数被执行,_regenerator
是从 @babel/runtime/regenerator
引入的,进入 @babel/runtime/regenerator
文件, 里面只有一行代码 :module.exports = require("regenerator-runtime");
,所以最终应该是 regenerator-runtime
库,直接找 wrap
方法
function wrap(innerFn, outerFn, self, tryLocsList) {
// If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
var protoGenerator = outerFn &&a