Babel 是如何转换 async/await 的?
一切的一切都要从 regeneratorRuntime is not defined
报错开始说起,在我们使用 es7 语法async/await
的时候,常常会利用 babel 进行兼容处理,在 babel 处理的过程中,如果useBuiltIns
(useBuiltIns 是配置 babel 如何处理 polyfills 的)设置为entry
(该设置为在入口处根据 target 引入 polyfills),并且没有引用插件 @babel/plugin-transform-runtime
就会生成这个报错。
我们抛开 babel 的配置不说,从头看看这个 regeratorRuntime 是个什么东西。为什么兼容 async 和 await 的时候会使用这个东西。
从异步编程开始说起
js 的异步编程从远古开始说起当然就是回调函数啦:假设 step1/step2/step3 都 return promise
- 1.0.石器时代:
!function(){
step1('options', function(res1) {
step2('options', function(res2) {
step3('options', function(res3) {
......
})
})
})
}()
这样就会造成 callback hell。
- 2.0.青铜时代:promise 的链式调用
step1()
.then((res1) => {
// do sth
return step2();
})
.then((res2) => {
//do sth
return step3();
})
.then((res3) => {
//do sth
});
- 3.0.现代 async/await:
async () => {
const res1 = await step1();
const res2 = await step2();
const res3 = await step3();
};
- 2.5.但其实在青铜时代和现代之间还出来了一个蒸汽时代,但是由于现代来的太快,马上就取代了蒸汽时代的产物:Generator
function* stepBystep() {
yield step1();
yield step2();
yield step3();
}
const pipeline = stepBystep();
let res1 = pipeline.next(); // {done: false, value: step1的返回值}
let res2 = pipeline.next(); // {done: false, value: step2的返回值}
let res3 = pipeline.next(); // {done: true, value: step3的返回值}
yield
有投降/屈服的意思,感觉就是“让出现在的执行权利”的意思。
自此我们比较一下2.5
和3,0
使用方法,感觉很相似,但是有几个不同点:
1.async/await 在执行完毕之后会自动往下执行,但是 generator 会停止等待手动调用next()方法
继续执行。(自动挡和手动挡) 2.如果你打印两者的 res1/res2/res3 话会发现在3.0
时代打印出来的是 promise,2.5
时代打印出来的是生成器对象
await/async = generator + promise
正如我们上面所说的,async/await 和 generator 的主要区别之一就是一个是自动挡,一个是手动挡,那么只要给 generator 加上一个自动换挡系统,那是不是在流程上就可以和 async/await 一样了?我们利用 promise 的 resolve 来充当这个自动挡系统: 还是上面的例子:
function* stepBystep() {
let step1 = yield Promise.resolve("step1"); // step1
console.log(step1);
let step2 = yield Promise.resolve("step2"); // step2
console.log(step2);
let step3 = yield Promise.resolve("step3"); // step3
console.log(step3);
}
// 自动挡系统添加函数如下:
function automaticRun(generator) {
let pipeline = generator();
let _next = (val) => {
let res = pipeline.next(val);
if (res.done) {
return res.value;
} else {
// 在每个step的then中执行下一步
res.value.then((val) => {
console.log(val);
_next(val); //next可以携带一个参数,可以作为在generator中上一个yeild(暂停的那个)表达式的返回值
});
}
};
_next();
}
automaticRun(stepBystep);
// automaticRun等于做了下面的这些事情:-----------------------
var manual = stepBystep();
let start = manual.next(); // 执行第一个yield,此时start是Promise.resolve('step1')的返回值
start.value.then((val) => {
let res1 = manual.next(val); // 执行第一个yield和第二个yield之间的方法,并将val设置为第一个yield的返回值
res1.value.then((val1) => {
let res2 = manual.next(val1); // 执行第二个yield和第三个yield之间的方法,并将val1设置为第二个yield的返回值
res2.value.then((val2) => {
let res3 = manual.next(val2); // 执行第三个yield到函数结束之前方法,并将val2设置为第三个yield的方法
});
});
});
正如上面的代码,我们利用 promise 和 generator 实现了类似于 await/async 的语法。感兴趣的同学可以去babel try it out写个 async/await 看一下 babel 的处理和我们实现的版本思路一致,只是做了额外的几点:
- 因为 async 最终返回的是 promise,所以最终会用 promise 包裹一层
- 我们直接假设 yeild(await)后面(res.value)一定跟着 promise,但其实他也可能不是,所以也需要用 promise 包裹一层
- 处理 generator 的 throw 和 catch promise 的异常。
regeneratorRuntime = babel 对 generator 的兼容处理
终于来到了这个报错,regeneratorRuntime 是什么呢?上一节的总结是async/await = generator + promise
又因为 generator 是 es6 才支持的 feature,所以 babel 同时也需要对 generator 进行兼容。。。
举个例子:
async function test() {
var result1 = await timer("result1");
var result2 = await timer("result2");
var result3 = await timer("result3");
return result1 + result2 + result3;
}
let timer = (value) => {
// 模拟一个简单的异步操作
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value);
}, 2000);
});
};
test().then((res) => console.log(res)); //大约在6s之后输出 result1result2result3
在babel try it out得到 babel 输出的结果如下:
function _asyncToGenerator(fn) {
// 此处省略,这里相当于上面我们自制的automaticRun的方法
}
function test() {
return _test.apply(this, arguments);
}
function _test() {
_test = _asyncToGenerator(
/*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
var result1, result2, result3;
return regeneratorRuntime.wrap(function _callee$(_context) {
// 省略。。
}, _callee);
})
);
return _test.apply(this, arguments);
}
var timer = function timer(value) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(value);
}, 2000);
});
};
test().then(function (res) {
return console.log(res);
});
_asyncToGenerator
相当于我们上面自己实现的‘自动挡’函数,在函数中我们又看到了不知道哪来的regeneratorRuntime.mark
和regeneratorRuntime.wrap
,又因为generator
其实是 es6 标准,如果你利用 babel 兼容 es5 语法的话,也就需要对generator
做语法兼容(其实感觉现在很少有不支持 es6 的情况了)。所以regeneratorRuntime
就是实现了一个 generator。这里附上regenratorRuntime 的完整实现整个实现过程很复杂,我们提炼出一个简单版的 Generator 实现过程。
Generator 模拟实现
先从结果出发,我们要完成的效果如下
// 如果定义的generator如下:
function* stepBystep() {
yield "result1";
yield "result2";
yield "result3";
}
// 那么使用的效果就该是这样:
const pipeline = stepBystep();
let res1 = pipeline.next(); // {done: false, value: 'result1'}
let res2 = pipeline.next(); // {done: false, value: 'result2'}
let res3 = pipeline.next(); // {done: false, value: 'result3'}
let res4 = pipeline.next(); // {done: true, value: undefined}
- 首先是执行
stepBystep
函数,得到一个带 next 方法的 generator,并且 next 返回一个 object
function stepBystep() {
return {
next: function() {
return {
value: null
done: null
}
}
}
}
- 因为每次执行完一次 next(),需要记录当下的执行情况,所以我们还需要定义一个 context 来记录现在的执行情况
function execution() {
//执行下一步
}
var context = {
step: 0
done: false
}
function stepBystep() {
return {
next: function() {
return {
value: context.done ? undefined : execution(context)
done: context.done
}
}
}
}
- execution 方法来执行每一步并确定输出值
function execution(_context) {
while (1) {
switch (_context.step) {
case 0:
_context.step = 2;
return "result1";
case 2:
_context.step = 4;
return "result2";
case 4:
_context.step = 6;
return "result3";
case 6:
_context.stop();
return undefined;
}
}
}
function stepBystep() {
var context = {
step: 0,
done: false,
stop: function stop() {
this.done = true;
},
};
return {
next: function () {
return {
value: context.done ? undefined : execution(context),
done: context.done,
};
},
};
}
//最终输出如下:
const pipeline = stepBystep();
let res1 = pipeline.next(); // {done: false, value: 'result1'}
let res2 = pipeline.next(); // {done: false, value: 'result2'}
let res3 = pipeline.next(); // {done: false, value: 'result3'}
let res4 = pipeline.next(); // {done: true, value: 'undefined'}
以上就是 regeratorRuntime 的超级简单版本,regeneratorRuntime.mark 对应的是在原型链上添加对象;regeneratorRuntime.wrap 做的事情就是我们上面所做的事情,将创建出来的 generator 添加 next 方法,创建 context 保存执行状态,最终模拟出一个 generator。
总结:
- generatorRuntime 是在
@babel/plugin-transform-runtime
插件中(旧版本是@babel/polyfill)中的内容,用作给 generator 做语法兼容。 - await/async 语法的兼容是利用 generator 和 promise 实现的,给手动挡汽车(generator)配上一个自动变速箱(promise)。