某次面试场景:
面试官:你知道 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 && outerFn.prototype instanceof

本文通过分析Babel的源码,揭示了Async和Generator函数的转换过程。从polyfill后的代码开始,逐步解析流程串连、流程解析,探讨如何处理异步和迭代。文章还提供了一个简易版的实现示例,以及对于for循环和Generator函数的讨论,强调了它们在处理异步控制流中的作用。
最低0.47元/天 解锁文章
880

被折叠的 条评论
为什么被折叠?



