首页 > 解决方案 > 我们的 await 关键字在 for await of 循环中的行为与普通异步函数中的行为有何不同?

问题描述

这是一个简单的异步函数:

async function f(){

    let data = await (new Promise(r => setTimeout(r, 1000, 200)));
    
    console.log(data)
     
    return data + 100;   
}

f()

console.log('me first');

这是该程序中将发生的事情:

  1. f() 将被调用
  2. 它进入 f 执行上下文并运行 await 表达式 - 因为函数调用的优先级高于 await 关键字,所以我们的新 Promise() 函数将首先运行,该函数将立即返回一个待处理的 Promise,然后 await 关键字将起作用。
  3. 我们设置了一个计时器,它将在 1 秒后解决我们的承诺
  4. 因为它还没有解决,await 将返回一个待处理的承诺,我们从当前执行上下文中跳出一层,这恰好是我们开始执行全局代码的全局执行上下文。
  5. 我们点击“console.log('me first')”行并记录“me first”
  6. 现在在 1 秒后,我们的 promise 已经解决,这意味着我们的 await 表达式后面的所有代码都将被放入一个容器中,并放入微任务队列中,以便在下一次执行时执行!(在引擎盖下它可能像生成器函数一样调用 .next() 方法,但我们不要深入讨论)
  7. 我们的同步代码已经完成,我们的微任务队列中有一个任务,我们把它放到我们的调用堆栈中运行!
  8. 它运行,一旦我们回到我们的异步函数,它就会从它离开的地方开始,即等待表达式。
  9. 由于我们的 promise 现在已经解析,我们的 await 关键字将从已解析的 promise 中提取值并将其分配给变量 d。
  10. 我们将该值记录到下一行。
  11. 最后,在最后一行,我们从 async 函数返回值 + 100,这意味着我们之前返回的待处理承诺(如第 4 行所述)将使用该值解决,即 300!

如您所见,此时我对异步等待非常满意!现在这是我们等待迭代的代码!

    async function f(){

        
        let p1 = new Promise(function (r){
            setTimeout(r, 2000, 1)
        })
        let p2 = new Promise(function (r){
            setTimeout(r, 1000, 2)
        })
        let p3 = new Promise(function (r){
            setTimeout(r, 500, 3)
        })
     
        let aop = [p1, p2, p3];
        
        for await (let elem of aop ){
            
            let data = await elem;
            console.log(data)
        }
        
    }    

f()
    
    console.log('global');

现在直观地说,假设 await 的行为与我们之前的 async 函数中的行为相同,您会期望这种情况发生!

  1. f() 被调用
  2. 我们创建了三个 Promise p1, p2, p3
  3. p1 将在 2 秒后解析,p2 在 1 秒后解析,p3 在 500 m/s 后解析
  4. 我们将这些 Promise 放在一个名为 aop 的数组中(因为 for 可以使用具有 Symbol.iterator 作为属性的对象,并且所有数组都具有该属性。)
  5. 现在这就是我觉得棘手的地方!
  6. 我们将遍历每个元素,对于第一个元素,我们进入循环并立即等待我们的第一个元素,即我们的第一个承诺 p1。
  7. 由于它还没有解决,它会像我们之前的示例一样返回一个待处理的承诺,或者是吗?
  8. 其他两个承诺 p2 和 p3 也会发生同样的情况,返回更多两个待处理的承诺。
  9. 我们的异步函数完成了,我们在全局执行中再往外一层,打印出 global: console.log('global');
  10. 至此,我们的 p3 Promise 已经解决了,因为它本来应该只需要 500ms 才能完成,所以它首先解决了!接着!也许在那之后我们留下的任何代码(console.log(data))都会被放入微任务队列以供以后执行?
  11. 按照这个逻辑,它应该打印 3,因为我们的最后一个承诺首先解决,然后是 2,然后是 1,但它打印 1、2、3!那么我在写作中遗漏了哪些关键点?因为 await 显然是平行的,所以它会通过每个元素而不等待第一个元素完成,那么为什么它会这样工作呢?

标签: javascriptasynchronousasync-awaitpromisescheduled-tasks

解决方案


当你得到一个承诺时,等待循环有特殊的行为。

记住 - 承诺是价值- 一旦你有一个承诺,创建该承诺的动作(在这种情况下设置计时器)已经发生。承诺就是该行动的价值+时间。

这里发生的实际上是:

  • 你将你的一系列承诺(只是已经开始的行动的结果)传递给for...await.
  • 由于该数组没有Symbol.asyncIterator- 循环检查它是否有Symbol.iterator. 由于数组是可迭代的并且具有该符号 - 它被调用。
  • 循环检查每个返回的for... await值是否是一个承诺 - 如果是,它会在进行循环之前等待它。

由于值是承诺 - 它们将被排序(也就是说,只有在前一个调用返回的前一个承诺已经解决后,才会在.next()从返回的迭代器上调用。[p1, p2, p3][Symbol.iterator]().next


笔记:

  • 您正在使用resolved但意思是settledor fulfilled

推荐阅读