都说async/await
是Generator
+Promise
的语法糖,通过本文逐步揭开async/await
背后的秘密...
一、使用Generator
把异步逻辑同步化
function asyncOp(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Number.isSafeInteger(x)
? resolve(12)
: reject(new Error('Invalid integer'));
}, 3000)
})
}
function *gen(x) {
try {
var y = yield asyncOp(x);
return x + y;
} catch(e) {
console.error(e)
}
}
var g = gen(1);
// 获取异步操作
var asyncAction = g.next();
asyncAction.value
.then(value => {
// 把异步操作的结果值传给生成器函数
var result = g.next(value);
console.log(result.value);
})
整体思路:
- 通过
yield
返回异步操作(并暂停生成器函数执行); - 通过
next
方法把异步操作的结果值传入生成器函数(并继续执行生成器函数);
相对于生成器函数里代码来说并不关心yield
表达式的值是同步还是异步。 - 如果异步操作失败了,可以跟通过
throw
方法在暂定位置抛异常。
var g = gen('a');
var asyncAction = g.next();
asyncAction.value
.catch(reason => {
// 通过throw告诉生成器函数异常操作发生了异常
g.throw(reason);
})
二、实践:异步加法
function getRadom() {
return (Math.random() * 100) >>> 0;
}
function getRandomAsync() {
return new Promise(resolve => {
setTimeout(() => {
resolve(getRadom());
}, 2000)
})
}
function* sum() {
var x = yield getRandomAsync();
console.log(`x=${x}`)
var y = yield getRandomAsync();
console.log(`y=${y}`)
return x + y;
}
1. 最搓的方式:逐步调用
var gen = sum();
// 获取异步操作1
gen.next().value
.then(val => {
// 把异步操作1的结果传给生成器函数,并获取异步操作2
gen.next(val).value
.then(val => {
// 把异步操作2的结果传给生成器函数,并获最终结果
var sum = gen.next(val).value;
console.log(`sum=${sum}`)
})
})
上面的写法就像记流水账,如果有3个数相加还这样写岂不是要疯。
2. 固定的模式的调用方式:
// 生成器函数执行器
function runner(genFunc) {
// 创建生成器对象
var gen = genFunc();
// 开启执行
return doRun();
function doRun(arg) {
// 把上一个异步操作结果`arg`传如生成器函数
var data = gen.next(arg);
if(data.done) {
return Promise.resolve(data.value);
}
// 如果还没结束,就等异步操作结束后递归调用doRun。
return Promise.resolve(data.value).then(doRun);
}
}
runner(sum).then(sum => {
console.log(sum)
})
注意:
这里使用Promise.resolve
方法把data.value
转成Promise
,因为Promise.resolve
的特殊功能:如果实参value是个Promise对象,则直接返回实参
三、实践:处理异步操作的异常
function runner(genFunc) {
var gen = genFunc();
return new Promise((resolve, reject) => {
doRun();
function doRun(arg) {
try {
// 捕获`next`方法抛出的异常
var data = gen.next(arg);
if(data.done) {
return resolve(data.value);
}
// 如果还没结束,就等异步操作结束后递归调用doRun。
return Promise.resolve(data.value)
.then(doRun)
.catch(gen.throw) // 通过`throw`方法告诉生成器异常了
.catch(reject) // 捕获`throw`方法抛出的异常(即生成器方法没有捕获处理异常)
} catch(error) {
reject(error);
}
}
})
}
runner(sum).then(sum => {
console.log(sum)
})
.catch(reason => {
console.error(reason)
})
- 异常不仅来自
throw
方法,next
方法也可能会抛出异常,所以在最外层使用try-catch
捕获next
方法抛出的异常; -
runner
方法的返回值也不再是doRun()
了,而改成了Promise
,用于处理next
方法抛出的异常和最终的结果值。
捕获throw
方法抛出的异常也可以采用try-catch
方式,这样就跟捕获next
方法抛出的异常保持一致了:
function runner(genFunc) {
var gen = genFunc();
return doRun();
function doRun(arg) {
return new Promise((resolve, reject) => {
step('next');
function step(methodName, arg) {
try {
// 捕获`next`方法抛出的异常
var data = gen[methodName](arg);
} catch(error) {
reject(error);
return;
}
if(data.done) {
return resolve(data.value);
}
return Promise.resolve(data.value)
.then(value => {
step('next', value);
})
.catch(reason => {
step('throw', reason);
})
}
})
}
}
runner(sum)
.then(sum => {
console.log(sum)
})
.catch(reason => {
console.error(reason)
})
四、实践:分析async/await
的Generator
+Promise
写法
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);
}
}
// 负责把`async`转成`generator`
function _asyncToGenerator(fn) {
return function () {
// 处理传给生成器的参数
var self = this,
args = arguments;
return new Promise(function (resolve, reject){
// 生成器的函数在Promise参数的回调函数里执行,并且处理参数
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);
});
};
}
使用固定的模式把async
转成Generator
,Babel的实现更严谨些:
- 可以传参数给生成器函数;
- 先转
Generator
,调用时再传参。
参考这个调整之前的实现:
function _asyncToGenerator(genFunc) {
return function() {
var args = arguments;
var self = this;
return new Promise((resolve, reject) => {
var gen = genFunc.apply(self, args);
step('next');
function step(methodName, arg) {
try {
var data = gen[methodName](arg);
} catch(error) {
reject(error);
return;
}
if(data.done) {
return resolve(data.value);
}
return Promise.resolve(data.value)
.then(value => {
step('next', value);
})
.catch(reason => {
step('throw', reason);
})
}
})
}
}
_asyncToGenerator(sum)()
.then(sum => {
console.log(sum)
})
.catch(reason => {
console.error(reason)
})
总结下async/await
转成Genertor
方式:
-
await
直接替换成yield
; -
async
函数体代码转成生成器代码(匿名的生成器函数); -
async
函数名被转成普通函数内部调用生成器函数的函数。
async function sum() {
var x = await 1;
var y = await 2;
return x + y;
}
// 对应的生成器方式
function sum() {
return _sum.apply(this, arguments);
}
function _sum() {
// 注意:函数体内的_sum变量是个局部变量,不影响外部作用域下的_sum的值。
_sum = _asyncToGenerator(function* () {
var x = yield 1;
var y = yield 2;
return x + y;
});
// 创建生成器对象,并开始执行生成器
return _sum.apply(this, arguments);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。