图解JavaScript中的async generator函数执行顺序

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函数管理,或者直接使用都行。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值