起源
为了在老工程中使用vue和react等,目前老项目使用rollup-to-nej这个工具打包。在观察打包生成后代代码的时候,发现有挺多的一些辅助函数,这些辅助函数会被重复的加入到我们打包生成的文件中,比如当我们使用async/await的时候。解决方法就是引入babel-runtime和babel-helper并修改babel配置来移除这些辅助函数。
当我比较修改之后的打包文件的时候,发现用到async/await的时候少了这么两个辅助函数。
那么到底打包的时候是如何实现async/await的呢?
实现
async/await实目前用的最多异步解决方案,它其实是Generator的一种语法糖,可以理解为一个自执行的generator。通常在babel或者ts打包为,一般会实现已下两步。
- 实现一个自执行的Generator
- 实现一个Generator(打包为es5)
实现一个自执行的Generator
通常来说我们通过已下两种方式实现:
babel和ts在打包的时候都是使用co模块的思路通过Promise来实现,将已下测试代码在ts中转为es5
async function doTest(asyncFn, name) {
console.log(0)
const result = await asyncFn(name);
console.log(result)
console.log(1)
await asyncFn()
}
"use strict";
// async/await的实现
// 参数generator就是一个generator函数
// 参数P默认传void 0,取Promise
var __awaiter =
(this && this.__awaiter) ||
function (thisArg, _arguments, P, generator) {
// adopt返回一个promise
function adopt(value) {
return value instanceof P
? value
: new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) {
// promise的fulfilled函数,会去调用generator.next
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
// promise的rejected函数,会去调用generator.throw
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
// step判断generater函数有没有执行完
function step(result) {
// generater函数执行完的result值的done为true,执行完则resolve()
// 否则生成一个Promise继续执行next
result.done
? resolve(result.value)
: adopt(result.value).then(fulfilled, rejected);
}
// 开始执行generator
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
// 原来的async和await会转换成一个generator实现
function doTest(asyncFn, name) {
return __awaiter(this, void 0, void 0, function* () {
console.log(0);
const result = yield asyncFn(name);
console.log(result);
console.log(1);
yield asyncFn();
});
}
看完ts的实现,在去看babel的实现(如图1中所示),会发现他其实就是多拆分成了一个函数而已,实现方法都是相同的。
实现一个Generator生成器
因为因为Generator是es6的新语法,所以在babel和ts中打包为es5的代码时需要去实现一个Generator生成器。生成器的强大之处在于能方便地对生成器函数内部的逻辑进行控制。在生成器函数内部,通过yield
或yield*
,将当前生成器函数的控制权移交给外部,外部通过调用生成器的next
或throw
或return
方法将控制权返还给生成器函数,并且还能够向其传递数据。
生成器并非由引擎从底层提供额外的支持,我们可以将生成器视为一个语法糖,用一个辅助工具将生成器函数转换为普通的Javascript代码,在经过转换的代码中,有两个关键点,一是要保存函数的上下文信息,二是实现一个完善的迭代方法,使得多个yield
表达式按序执行,从而实现生成器的特性。
在babel中实现一个Generator
function _doTest() {
_doTest = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee(asyncFn, name) {
var result;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log(0);
_context.next = 3;
return asyncFn(name);
case 3:
result = _context.sent;
console.log(result);
console.log(1);
_context.next = 8;
return asyncFn();
case 8:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _doTest.apply(this, arguments);
}
-
yield被转换为switch case,_context保存着当前函数的上下文状态
我们可以把switch case看做看做是一个状态机,根据_context的状态来执行不同的代码。
函数被regeneratorRuntime.mark包装,返回一个被regeneratorRuntime.wrap包装的迭代器对象。
简单来说,我们可以理解为实现了一个带有next\return\throw等属性的一个迭代器对象,babel中使用regenerator生成。如果了解regenerator的实现,可以看一下ES6 系列之 Babel 将 Generator 编译成了什么样子
我么也可以简单的看下ts中generator的实现。
var __generator = (this && this.__generator) || function (thisArg, body) {
// _保存着generator的上下文状态
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
// 返回一个遍历器对象
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
// 调用step
function verb(n) { return function (v) { return step([n, v]); }; }
//根据op调用,首次执行时op为[0, undefined],后续为body的返回值
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
//
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
// 返回未完成
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
//每次next调用后都去执行body,_为上下文状态
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
// 执行完成 此时op[0]为2,0b10 & 0b101 为0。
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
function doTest(asyncFn, name) {
return __awaiter(this, void 0, void 0, function () {
var result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
console.log(0);
return [4 /*yield*/, asyncFn(name)];
case 1:
result = _a.sent();
console.log(result);
console.log(1);
return [4 /*yield*/, asyncFn()];
case 2:
_a.sent();
return [2 /*return*/];
}
});
});
}
它的实现也遵循了使用switch case
来实现yield,_
来保存上下文状态。在__generator
函数中实现了一个迭代器对象,有着next,throw,return等方法。