Open yaofly2012 opened 6 years ago
都说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); })
# 二、实践:异步加法
```js
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;
}
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个数相加还这样写岂不是要疯。
function genFunctionRunner(genFunc) {
return new Promise(function(resolve, reject) {
// 创建生成器对象
var gen = genFunc();
// 开启执行
doRun();
function doRun(val) {
try {
var p = gen.next(val);
//
if(p.done) {
resolve(p.value)
return;
}
Promise.resolve(p.value)
.then(doRun) // 把上一个异步操作成功结果传给生成器对象,并恢复执行
.catch(gen.throw) // 把上一个异步操作失败结果传给生成器对象,并恢复执行
} catch(e) {
reject(e)
}
}
})
}
genFunctionRunner(sum).then(console.log)
注意:
genFunctionRunner
函数的关键是自执行和利用递归获取下一个yield
返回值。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(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 genFunctionRunner(genFunc) {
return new Promise(function(resolve, reject) {
// 创建生成器对象
var gen = genFunc();
// 开启执行
doRun();
function doRun(method, arg) {
try {
var p = gen[method](arg);
//
if(p.done) {
resolve(p.value)
return;
}
Promise.resolve(p.value)
.then(val => {
doRun('next', val);
})
.catch(reason => {
doRun('throw', reason);
})
} catch(e) {
reject(e)
}
}
})
}
genFunctionRunner(sum).then(console.log)
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
,调用时再传参;yiled
操作的函数没有直接定义在async
转生成器的函数里,省得每次调用都生成一个函数(高,实在是高)。async/await
转成Generator
函数方式: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);
}
var a = 0
function* gen(x) {
a = a + (yield x);
console.log(3, a)
}
var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);
知识点:
一、Generator function(生成器函数)
1.1 语法
格式上跟普通函数就多个了星号
*
; 星号*
和function
,函数名之间可以没有空格,一般把星号和function
放一起。生成器函数返回值是个生成器对象
var genObj = gen(); console.log(typeof gen) // function console.log(toString.call(gen)) // [object GeneratorFunction] console.log(gen instanceof Function) // true console.log(toString.call(genObj)) // [object Generator] console.log(typeof genObj) // object
yield
关键字把生成器函数分割成一段段可执行代码片段,调用next
方法时就执行其中的代码片段return
和throw
方法会立即终止,不会再次执行。return
语句表示生成器函数最终的值var g = gen();
console.log(g.next()) // {value: 1, done: false} console.log(g.next()) // {value: 2, done: false} console.log(g.next()) // {value: 3, done: true} console.log(g.next()) // {value: undefined, done: true}
yield*
写法表示遍历其他生成器对象function generator(i) { yield i; yield anotherGenerator(i); yield i + 10; }
var gen = generator(10);
console.log(gen.next().value); // 10 console.log(gen.next().value); // 11 console.log(gen.next().value); // 12 console.log(gen.next().value); // 13 console.log(gen.next().value); // 20
本质上
yield*
后面可以是任意可迭代对象二、生成器对象(Generator)
生成器对象是一个内部含有状态的对象,通过并且只能通过生成器函数创建。
2.1 语法
生成器函数的返回值。
Object.prototype.toString
判断生成器函数和生成器对象类型var a = { count: 1 }; var g = gen(a)
Object.prototype.toString.call(gen) // [object GeneratorFunction] Object.prototype.toString.call(g) // [object Generator]
// 可迭代对象 var arr = [...g] console.log(arr) // [2, 3, 4] console.log(Symbol.iterator in g) // true
2.3 APIs
1. next(value)
next
方法用于获取生成器对象下一个状态,即yield
后面表达式的值 ;value
可以指定生成器函数里yield
表达式的值 (注意区分“yield后面表达式的值”和yield
表达式本身的值)var a = foo(5); a.next() // Object{value:6, done:false},yield表达式的值为undefined a.next() // Object{value:NaN, done:false},yield表达式的值为undefined a.next() // Object{value:NaN, done:true},yield表达式的值为undefined
var b = foo(5); b.next() // { value:6, done:false },yield表达式的值为undefined b.next(12) // { value:8, done:false },yield表达式的值为12 b.next(13) // { value:42, done:true },yield表达式的值为13
return
方法可以多次调用,返回值的value属性是调用时的实参; 这个跟next
方法的返回值逻辑不一样。return
方法时,生成器函数从上一次暂定的地方或者函数开始处就结束(即后面的代码不会再执行);var g = gen(); // 立马结束,生成器函数没有执行代码的机会 console.log(g.return(1)) // {value: 1, done: true}
3. throw(exception)
让生成器函数在上次暂停的地方(或者函数开始处)抛出指定异常(throw方法实参)。
throw
返回值的取值逻辑同next
方法; 可以把throw
方法视为抛出异常的next
方法。var gg = gen(); console.log(gg.next()); console.log(gg.next()); console.log(gg.next()); console.log(gg.throw('error occured'));
如果要捕获生成器没有捕获的异常,得在调用
throw
处捕获:4. 总结
next/return/throw
方法都是用于恢复生成器函数执行,区别是:next
:继续执行;return
:终止执行,并返回指定的值;throw
:终止执行,并抛出指定的异常。这三个方法是外部控制生成器对象的执行,可用于实现异步逻辑同步化关键。
用“语句替换”描述只是便于理解吧,但是有点误导。起码
return
和throw
方法可以在函数开始处立马结束(生成器函数都没有执行)三、生成器原理
先看看下面输出是什么:
稍微调整代码,下面输出是什么:
再稍微调整代码,下面输出是什么:
执行下文会被暂存 遇到
yield
表达式时,生成器函数暂停执行,退出调用栈,但是执行下文会被暂存。恢复执行 暂存的执行上下文也被恢复。
回到上面的问题Demo1和Demo2的结果为啥不一样? 在
Demo1
中语句a = a + (yield x);
算术运算从左向右执行的,等执行到yield x
时变量a
已经参与运算了,暂停执行的时候,会存在临时变量里了。相当于:参考