ES6中的async generator函数很好用,在处理一些列的异步调用管理上很方便。可以方便的处理异步函数,可以用来管理异步的、序列的异步工作流。但是执行的顺序确实有点绕,脑子不太好使,端午节期间花了一天的时间来彻底梳理了一下。
注意,看这篇文章前,最好要先了解generator函数的运行机制和基本的使用。这篇文章不做阐述。
直接撸代码
一、generator函数中,直接使用yield返回
// 模拟一个异步函数
function mockFetch(url, ms){
log(`mockFetch enter :: url = ${url}, ms = ${ms}`); // A1行
//return ms;
return new Promise((resolve, reject)=>{
log(`mockFetch new promise enter :: url = ${url}, ms = ${ms}`); // A2行
setTimeout(()=>{
// 模拟resove和reject场景
if(ms % 2 ===0){
resolve(`mockFetch end :: url = ${url}, ms = ${ms}`); // A3行
}else{
reject(new Error("mockFetch :: error, not support!!")) // A4行
}
},ms || 600);
})
}
// 异步生成函数
async function* asyncGen(){
log(`asyncGen :: enter.`); // B1 行
yield mockFetch("google.com",800); // B2 行
yield mockFetch("taobao.com",600); // B3 行
yield mockFetch("bd.com",100);// B4 行
return "asyncGen end."; // B5 行
}
// 测试使用异步生成函数
function testAsyncGen2(){
log(`testAsyncGen2 :: enter.`); // C1 行
let ag = asyncGen(); // C2 行
let rst1 = ag.next();// C3 行
log(`testAsyncGen2 :: rst1 = ${rst1}.`);// C4 行
let rst2 = ag.next();// C5 行
log(`testAsyncGen2 :: rst2 = ${rst2}, ${rst1 ===rst2}`);// C6 行
let rst3 = ag.next(); // C7 行
log(`testAsyncGen2 :: rst3 = ${rst3}, ${rst1 ===rst2}, ${rst3 ===rst2}`);// C8 行
let rst4 = ag.next(); // C9 行
log(`testAsyncGen2 :: rst4 = ${rst4}.`);// C10 行
}
testAsyncGen2();
// 以下是执行顺序,很重要,也是很坑的地方
[16:21:38:788] testAsyncGen2 :: enter. // C1 行
[16:21:38:796] asyncGen :: enter. //B1
[16:21:38:797] mockFetch enter :: url = google.com, ms = 800 // A1
[16:21:38:797] mockFetch new promise enter :: url = google.com, ms = 800 //A2
[16:21:38:798] testAsyncGen2 :: rst1 = [object Promise]. // C4 行
[16:21:38:798] testAsyncGen2 :: rst2 = [object Promise], false // C6 行
[16:21:38:798] testAsyncGen2 :: rst3 = [object Promise], false, false // C8 行
[16:21:38:798] testAsyncGen2 :: rst4 = [object Promise]. // C10 行
[16:21:39:603] mockFetch enter :: url = taobao.com, ms = 600 // A1
[16:21:39:603] mockFetch new promise enter :: url = taobao.com, ms = 600 //A2
[16:21:40:208] mockFetch enter :: url = bd.com, ms = 100 // A1
[16:21:40:208] mockFetch new promise enter :: url = bd.com, ms = 100 //A2
从上面的执行结果可以看出来:异步生成函数,在执行它的next的时候,是按顺序执行完成一个再执行一个的,是串行的执行,并不是并行的执行!!! 这个比较坑,一定要注意。跟进上述测试结果,基本可以推测,异步执行函数的执行器是这样的(以上述方法为示例讲解):
1)调用异步生成函数的时候,返回的是一个异步迭代器。
2)异步迭代器在执行next方法的时候,方法先返回了一个promise对象,然后开始启动上述的异步生成器方法:此时会输出B1行:
[16:21:38:796] asyncGen :: enter. // B1 行
3)函数执行到B2行,遇到yiled表达式,开始调用mockFetch方法,此时执行器进入mockFetch,执行到A1、A2行:
[16:21:38:797] mockFetch enter :: url = google.com, ms = 800 // A1 行
[16:21:38:797] mockFetch new promise enter :: url = google.com, ms = 800 // A2 行
4)mockFetch函数返回Promise对象(暂时叫mfPromise)给B2行的yiled,此时不会再将mfPromise返回给next。
5)执行器进入C4行输出日志,接着进入C5行调用next,注意,注意,此时调用next并没有真正的进入B3行,而是压入了一个执行队列中(有点像事件循环的),因此此时并没有调用mockFetch函数
6)执行器进入C6行输出日志,接着进入C7行调用next,此时调用next也没有真正的进入B3行,而是压入了一个执行队列中,因此此时并没有调用mockFetch函数
7),因此C5~C10行,顺序执行,调用next的地方,都没有真正的调用,而是压入了一个执行任务队列。等待第一个任务执行完成之后再顺序执行,因此紧挨着输出日志:
[16:21:38:798] testAsyncGen2 :: rst1 = [object Promise]. // C4 行
[16:21:38:798] testAsyncGen2 :: rst2 = [object Promise], false // C6 行
[16:21:38:798] testAsyncGen2 :: rst3 = [object Promise], false, false // C8 行
[16:21:38:798] testAsyncGen2 :: rst4 = [object Promise]. // C10 行
8),800毫秒之后(第一个异步的mock异步时间)mockFetch返回的promise mfPromise 状态变为fullfilled,此时才会将第一个返回的promise rst1的状态也变为fullfilled 的,同时准备回调rst1.then【本次示例没写then哈】
9),紧接着从任务队列里面取出第二次调用next那个任务,开始执行next,此时才进入asyncGen预计的第二个yield部分,开始第二次调用mockFetch,然后进入上述步骤4~8:
[16:21:39:603] mockFetch enter :: url = taobao.com, ms = 600 // A1
[16:21:39:603] mockFetch new promise enter :: url = taobao.com, ms = 600 //A2
10),600毫秒之后,第二次mockFetch返回的promise mfPromise 状态变为fullfilled,此时会将第二次next返回的promise rst2的状态也变为fullfilled 的,同时准备回调rst2.then【本次示例没写then哈】
函数的调用时序图(图中的9、11、14等步骤是JS解释引擎处理的):
二、generator函数中,结合await使用
先上代码:
//为了方便在node中log出来的时候看时间,简单找了个以前封装的日志方法,就只是在前面加上了时间
let {log} = require("../../util/logger");
// 模拟异步获取远程数据
function mockFetch(url, ms){
log(`mockFetch enter :: url = ${url}, ms = ${ms}`);
return new Promise((resolve, reject)=>{// 对应时序图的 7
log(`mockFetch new promise enter :: url = ${url}, ms = ${ms}`);
setTimeout(()=>{
// 模拟resove和reject场景
if(ms % 2 ===0){
resolve(`mockFetch end :: url = ${url}, ms = ${ms}`);
}else{
reject(new Error("mockFetch :: error, not support!!"))
}
},ms || 600);
})
}
// 异步生成函数,注意,这里yield前面使用了await了,yield的是一个具体的结果了。
async function* asyncGenWithWait(){
log(`asyncGenWithWait :: enter.`); // 对应时序图的: 5
let mockRst1 = await mockFetch("google.com",800); // 对应时序图的: 6
yield "Result mockRst1===>>> "+ mockRst1; // 放到{value:xxx}对象上返回。
let mockRst2 = await mockFetch("taobao.com",600); // 对应时序图的: 14、15
yield "Result mockRst2===>>> "+ mockRst2;
let mockRst3 = await mockFetch("bd.com",100);
yield "Result mockRst3===>>> "+ mockRst3;
return "asyncGenWithWait end.";
}
// 测试使用异步生成函数,这里是否使用async没关系
async function testAsyncGenWait(){
log(`testAsyncGenWait :: enter.`);
let ag = asyncGenWithWait(); // 对应时序图的 1. call, 2, 返回异步迭代器ag
log(`testAsyncGenWait :: called asyncGenWithWait`);
let rst1 = ag.next(); // 对应时序图的 3、4
log(`testAsyncGenWait :: rst1 = ${rst1}.`);
rst1.then(({value, done})=>{log(`testAsyncGenWait :: rst1, value = ${value}, done = ${done}`)});
let rst2 = ag.next(); // 对应时序图的 8
log(`testAsyncGenWait :: rst2 = ${rst2}, ${rst1 ===rst2}`); // false
rst2.then(({value, done})=>{log(`testAsyncGenWait :: rst2, value = ${value}, done = ${done}`)});
let rst3 = ag.next(); // 对应时序图的 10
log(`testAsyncGenWait :: rst3 = ${rst3}, ${rst1 ===rst2}, ${rst3 ===rst2}`); // false
rst3.then(({value, done})=>{log(`testAsyncGenWait :: rst3, value = ${value}, done = ${done}`)});
let rst4 = ag.next();
log(`testAsyncGenWait :: rst4 = ${rst4}.`);
rst4.then(({value, done})=>{log(`testAsyncGenWait :: rst4, value = ${value}, done = ${done}`)});
}
testAsyncGenWait();
执行结果:
[22:43:51:608] testAsyncGenWait :: enter.
[22:43:51:650] testAsyncGenWait :: called asyncGenWithWait
[22:43:51:651] asyncGenWithWait :: enter.
[22:43:51:651] mockFetch enter :: url = google.com, ms = 800
[22:43:51:651] mockFetch new promise enter :: url = google.com, ms = 800
[22:43:51:652] testAsyncGenWait :: rst1 = [object Promise].
[22:43:51:652] testAsyncGenWait :: rst2 = [object Promise], false
[22:43:51:652] testAsyncGenWait :: rst3 = [object Promise], false, false
[22:43:51:652] testAsyncGenWait :: rst4 = [object Promise].
[22:43:52:457] mockFetch enter :: url = taobao.com, ms = 600
[22:43:52:457] mockFetch new promise enter :: url = taobao.com, ms = 600
[22:43:52:457] testAsyncGenWait :: rst1, value = Result mockRst1===>>> mockFetch end :: url = google.com, ms = 800, done = false
[22:43:53:061] mockFetch enter :: url = bd.com, ms = 100
[22:43:53:061] mockFetch new promise enter :: url = bd.com, ms = 100
[22:43:53:062] testAsyncGenWait :: rst2, value = Result mockRst2===>>> mockFetch end :: url = taobao.com, ms = 600, done = false
[22:43:53:166] testAsyncGenWait :: rst3, value = Result mockRst3===>>> mockFetch end :: url = bd.com, ms = 100, done = false
[22:43:53:167] testAsyncGenWait :: rst4, value = asyncGenWithWait end., done = true
这个执行顺序其实跟第一个的基本一样,直接上个时序图清晰的展示一下:
(JS解释引擎这里线咋看着有点不得劲。。。)
三、直接用作异步迭代器
接着第二步的继续,异步迭代器,就如其名字一样,可以当迭代器使用,串行执行。
//testAsyncGenWait();
async function testIteratorAsyncGenWait(){
log(`testIteratorAsyncGenWait :: enter.`);
let ag = asyncGenWithWait(); //
for await(let rst of ag){
log(`testIteratorAsyncGenWait :: in for await, rst = ${rst}`);
}
}
testIteratorAsyncGenWait();
输出:
[23:15:59:141] testIteratorAsyncGenWait :: enter.
[23:15:59:162] asyncGenWithWait :: enter.
[23:15:59:162] mockFetch enter :: url = google.com, ms = 800
[23:15:59:162] mockFetch new promise enter :: url = google.com, ms = 800
[23:15:59:969] testIteratorAsyncGenWait :: in for await, rst = Result mockRst1===>>> mockFetch end :: url = google.com, ms = 800
[23:15:59:969] mockFetch enter :: url = taobao.com, ms = 600
[23:15:59:969] mockFetch new promise enter :: url = taobao.com, ms = 600
[23:16:00:572] testIteratorAsyncGenWait :: in for await, rst = Result mockRst2===>>> mockFetch end :: url = taobao.com, ms = 600
[23:16:00:573] mockFetch enter :: url = bd.com, ms = 100
[23:16:00:573] mockFetch new promise enter :: url = bd.com, ms = 100
[23:16:00:675] testIteratorAsyncGenWait :: in for await, rst = Result mockRst3===>>> mockFetch end :: url = bd.com, ms = 100
四、如果串行执行异步效率低,如何并行执行。
在有的场景下,各异步的调用间不存在先后关系,但是都需要执行完了。此时就不要用async generator函数,可以直接用generator函数管理,或者直接使用都行。