javascript - 我们的 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');
这是该程序中将发生的事情:
- f() 将被调用
- 它进入 f 执行上下文并运行 await 表达式 - 因为函数调用的优先级高于 await 关键字,所以我们的新 Promise() 函数将首先运行,该函数将立即返回一个待处理的 Promise,然后 await 关键字将起作用。
- 我们设置了一个计时器,它将在 1 秒后解决我们的承诺
- 因为它还没有解决,await 将返回一个待处理的承诺,我们从当前执行上下文中跳出一层,这恰好是我们开始执行全局代码的全局执行上下文。
- 我们点击“console.log('me first')”行并记录“me first”
- 现在在 1 秒后,我们的 promise 已经解决,这意味着我们的 await 表达式后面的所有代码都将被放入一个容器中,并放入微任务队列中,以便在下一次执行时执行!(在引擎盖下它可能像生成器函数一样调用 .next() 方法,但我们不要深入讨论)
- 我们的同步代码已经完成,我们的微任务队列中有一个任务,我们把它放到我们的调用堆栈中运行!
- 它运行,一旦我们回到我们的异步函数,它就会从它离开的地方开始,即等待表达式。
- 由于我们的 promise 现在已经解析,我们的 await 关键字将从已解析的 promise 中提取值并将其分配给变量 d。
- 我们将该值记录到下一行。
- 最后,在最后一行,我们从 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 函数中的行为相同,您会期望这种情况发生!
- f() 被调用
- 我们创建了三个 Promise p1, p2, p3
- p1 将在 2 秒后解析,p2 在 1 秒后解析,p3 在 500 m/s 后解析
- 我们将这些 Promise 放在一个名为 aop 的数组中(因为 for 可以使用具有 Symbol.iterator 作为属性的对象,并且所有数组都具有该属性。)
- 现在这就是我觉得棘手的地方!
- 我们将遍历每个元素,对于第一个元素,我们进入循环并立即等待我们的第一个元素,即我们的第一个承诺 p1。
- 由于它还没有解决,它会像我们之前的示例一样返回一个待处理的承诺,或者是吗?
- 其他两个承诺 p2 和 p3 也会发生同样的情况,返回更多两个待处理的承诺。
- 我们的异步函数完成了,我们在全局执行中再往外一层,打印出 global: console.log('global');
- 至此,我们的 p3 Promise 已经解决了,因为它本来应该只需要 500ms 才能完成,所以它首先解决了!接着!也许在那之后我们留下的任何代码(console.log(data))都会被放入微任务队列以供以后执行?
- 按照这个逻辑,它应该打印 3,因为我们的最后一个承诺首先解决,然后是 2,然后是 1,但它打印 1、2、3!那么我在写作中遗漏了哪些关键点?因为 await 显然是平行的,所以它会通过每个元素而不等待第一个元素完成,那么为什么它会这样工作呢?
解决方案
当你得到一个承诺时,等待循环有特殊的行为。
记住 - 承诺是价值- 一旦你有一个承诺,创建该承诺的动作(在这种情况下设置计时器)已经发生。承诺就是该行动的价值+时间。
这里发生的实际上是:
- 你将你的一系列承诺(只是已经开始的行动的结果)传递给
for...await
. - 由于该数组没有
Symbol.asyncIterator
- 循环检查它是否有Symbol.iterator
. 由于数组是可迭代的并且具有该符号 - 它被调用。 - 循环检查每个返回的
for... await
值是否是一个承诺 - 如果是,它会在进行循环之前等待它。
由于值是承诺 - 它们将被排序(也就是说,只有在前一个调用返回的前一个承诺已经解决后,才会在.next()
从返回的迭代器上调用。[p1, p2, p3][Symbol.iterator]()
.next
笔记:
- 您正在使用
resolved
但意思是settled
orfulfilled
。
推荐阅读
- c++ - 关于 clang-format 规则:“public:应该在类内缩进 +1 个空格”
- r - 带多列的条形图
- c - 是否可以不使用OpenGL之类的图形库直接调用图形驱动程序?
- kivy - 有没有办法用按钮和文本框移动 kivy 滑块?
- javascript - 为什么 NodeJs 集群模块不起作用?
- c++ - 如何访问加载到 ID3D11ShaderResourceView 中的动画 GIF 的各个帧?
- javascript - 将数字数组转换为大数的数字会忽略一些数字
- python - 如何在python中将50显示为5√2的平方根
- javascript - JavaScript中的正则表达式,我可以只替换内部特定组而不是完整字符串吗?
- jmeter - 如何调整 JMeter 重复请求?