async跟await是javascript中常用的关键字,用于使用同步代码实现异步功能,但由于async/await是ES7标准中定义的,虽然很多浏览器早先实现了async/await,但很多时候还是需要考虑兼容性问题,它的兼容性如下:
一般来说,使用babel等对代码进行编译就可以避免兼容性问题,那它是怎么转换的呢,答案是使用生成器函数:
假设我们有下面的代码:
async function a(name) {
let ret = await b(name);
console.log(ret);
return ret + ' and Mark';
}
function b(name) {
return Promise.resolve('name: ' + name);
}
a('Bill').then(value => console.log(value));
来看看转换的结果:
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);
}
}
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);
});
};
}
function a() {
return _a.apply(this, arguments);
}
function _a() {
_a = _asyncToGenerator(function* (name) {
var ret = yield b(name);
console.log(ret);
return ret + ' and Mark';
});
return _a.apply(this, arguments);
}
function b(name) {
return Promise.resolve('name: ' + name);
}
a('Bill').then(value => console.log(value));
由于babel转换的代码包含了很多边界情况,所以显得逻辑复杂,我们将它简化就可以明白它的主要逻辑了:
- 将async函数转换为生成器函数(GeneratorFunction) 还是假设我们有a和b函数,转换之后的样子如下
function* _a(name) {
let ret = yield b(name);
console.log(ret);
return a + ' and Mark';
}
function b(name) {
return Promise.resolve('name: ' + name);
}
为了不影响原始代码,我们将_a函数包裹在a函数内部:
function a(name) {
var gen = _a(name);
var ret = gen.next();
// ret.value 是b()的返回值
return ret.value.then(v => {
var ret = gen.next(v);
// 此时的ret.value是_a()函数的返回值
return ret.value;
});
}
好了,像上面这样确实已经可以实现主要的功能了,但是存在一个问题,就是每出现一个yield就要嵌套一层then,这个过程如果能够自动化那么代码会简洁不少,于是我们就有了runGen和step函数:
- 实现step函数
function step(gen, value) {
const ret = gen.next(value);
if (ret.done) {
return ret.value;
} else {
return ret.value.then((v) => {
return step(gen, v);
});
}
}
function runGen(genFn, ...args) {
let gen = genFn(...args);
return step(gen);
}
function a(name) {
return runGen(_a, name);
}
- 运行结果
a('Bill').then(v => {
console.log(v);
});
好了,到这里就差不多实现了一个类似async函数的生成器函数了。
仔细看看会发现,babel实现的_asyncToGenerator
这个函数(相当于上文的runGen)返回的是一个new Promise()
,并且分别实现了_next和_throw函数,这可以更好地捕获处理生成器中的错误。