这是 for 循环如何无序执行其迭代器的?



我在async函数中使用for循环,循环中涉及await。从我读到的内容来看,for 循环应该与await很好地配合,但似乎有一些我不理解的异常行为。

考虑所描述的 func,将其简化为场景的基本原理:

let counter = 0;
module.exports = async () => {
console.log("processing iteration", counter); // just here as 
// a sanity check to make sure multiple funcs aren't running 
// concurrently to produce the out-of-order for loop iteration
counter++;
let keys = [... some array here ...]
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
console.log("processing iteration: [", counter, "] index: [", i, "]")
if (keys[key] === false) {
break;
} else {
if (Array.isArray(keys[key])) {
let x = await doStuff(parseInt(key));
// as suggested in an answer, I tried assigning the result 
// to a variable to create a closure, but nothing changes 
// in the output
doOtherStuff(parseInt(key));
delete keys[key];
}
}
}
}

当我在doStuff之前省略await时,正如预期的那样,这些东西会按照所需的顺序完成,但数组本身会正确循环,每个索引都按预期执行。日志如下所示:

processing iteration 125
processing iteration: [ 126 ] index: [ 0 ]
processing iteration: [ 126 ] index: [ 1 ]
processing iteration: [ 126 ] index: [ 2 ]
processing iteration: [ 126 ] index: [ 3 ]
processing iteration: [ 126 ] index: [ 4 ]

但是,如果我确实使用await,它需要用于按顺序完成这些异步任务(doStuff任务涉及数据库读取,所以我无法使它们同步(,就会发生疯狂。

具体来说,一旦使用了await,首先当调用函数时,它几乎可以按预期工作:

processing iteration 15
processing iteration: [ 16 ] index: [ 0 ]
processing iteration: [ 16 ] index: [ 1 ]
processing iteration: [ 16 ] index: [ 2 ]
processing iteration: [ 16 ] index: [ 3 ]
processing iteration: [ 16 ] index: [ 4 ]
processing iteration: [ 16 ] index: [ 5 ]
processing iteration: [ 16 ] index: [ 6 ]
processing iteration: [ 16 ] index: [ 7 ]
processing iteration: [ 16 ] index: [ 8 ]
processing iteration: [ 16 ] index: [ 9 ]
processing iteration: [ 16 ] index: [ 10 ]
processing iteration: [ 16 ] index: [ 11 ]
processing iteration: [ 16 ] index: [ 12 ]
processing iteration: [ 16 ] index: [ 13 ]

但是随着模拟的进行,这个函数被调用了很多次,虽然这些日志显示它在第 16 次迭代时运行良好,但一旦doStuff内部发生的事情发生了变化(并且很难解释内部到底发生了什么变化,但一旦满足某些条件,其他处理和循环就会开始在内部发生(,迭代器开始多次执行:

processing iteration 147
processing iteration: [ 148 ] index: [ 0 ]
processing iteration: [ 148 ] index: [ 1 ]
... something changes here with what's happening inside of doStuff ..
processing iteration: [ 148 ] index: [ 1 ]
processing iteration: [ 148 ] index: [ 1 ]
processing iteration: [ 148 ] index: [ 1 ]
processing iteration: [ 148 ] index: [ 1 ]
processing iteration: [ 148 ] index: [ 1 ]
processing iteration 148
processing iteration: [ 149 ] index: [ 0 ]

然后,不仅索引重复,而且在以下 func 迭代计数中,for循环开始不按循环顺序迭代:

processing iteration 148
processing iteration: [ 149 ] index: [ 0 ]
processing iteration 149
processing iteration: [ 150 ] index: [ 0 ]
processing iteration: [ 150 ] index: [ 2 ]
processing iteration: [ 150 ] index: [ 2 ]
processing iteration: [ 150 ] index: [ 2 ]
processing iteration: [ 150 ] index: [ 2 ]
processing iteration: [ 150 ] index: [ 1 ]
processing iteration: [ 150 ] index: [ 2 ]
processing iteration 150
processing iteration: [ 151 ] index: [ 0 ]
processing iteration 151
processing iteration: [ 152 ] index: [ 0 ]
processing iteration: [ 152 ] index: [ 2 ]
processing iteration 152
processing iteration: [ 153 ] index: [ 0 ]

关于for loopsasyncawait有什么我根本无法理解的,只需查看这个删节版的代码就可以解释吗?我一定缺少一些东西,因为在我看来,在使用 await 时,那些for循环迭代器绝对不可能乱序打印。

如果需要,我将发布代码的完整版本,包括doStuff中发生的事情,我只是删节了它以使问题尽可能简洁,除非需要更多细节。

请注意 9000 个日志,以及发生这种情况时索引的细分。 外部函数是异步的,因此它们被放入事件循环中,并将与事件循环中的 await 调用混合在一起。
因此,等待之后的下一次迭代最终会在计数器已经递增后使用计数器的值。
另请注意,输出在 1 秒后完全发生。

关键点是 await 就像一个"yield",它导致它退出函数并产生一个 Promise,并将其放在事件循环中。

因此,可能发生的情况是,当命中 await 时,执行被延迟,因此计数器在事件循环中等待等待位置时会更改。

for(let i=0;i<10;i++){
counter2++
module.exports()
}
counter2 = 9000
<script>
var module = {},doStuff=()=>new Promise(r=>Math.random()<0.5?setTimeout(r,1000):r()),doOtherStuff=()=>{}
var counter2 = 0
let counter = 0;
module.exports = async () => {
console.log("processing iteration", counter); // just here as 
// a sanity check to make sure multiple funcs aren't running 
// concurrently to produce the out-of-order for loop iteration
counter++;
console.log(counter2)
let keys = [1,2,6,6,1,6,[1],[1],10]
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
console.log("processing iteration: [", counter, "] index: [", i, "]")
if (keys[key] === false) {
break;
} else {
if (Array.isArray(keys[key])) {
let x = await doStuff(parseInt(key));
console.log(counter2)
// as suggested in an answer, I tried assigning the result 
// to a variable to create a closure, but nothing changes 
// in the output
doOtherStuff(parseInt(key));
delete keys[key];
}
}
}
}
</script>

等待对 module.exports(( 的所有调用,因此执行顺序符合您期望的顺序(每次对 module.exports(( 的调用在下一次继续之前完成(

// every call to module.exports() is awaited on,
// so it won't continue until the last one is finished
// and execution flow will behave as you are expecting
(async()=>{
for(let i=0;i<10;i++){
counter2++
await module.exports()
}
})()
counter2 = 9000
// notice how counter2 is 1 at the beginning of the first call to module.exports()
// and then 9000 after the await in module.exports()
<script>
var module = {},doStuff=()=>new Promise(r=>Math.random()<0.5?setTimeout(r,1000):r()),doOtherStuff=()=>{}
var counter2 = 0
let counter = 0;
module.exports = async () => {
console.log("processing iteration", counter); // just here as 
// a sanity check to make sure multiple funcs aren't running 
// concurrently to produce the out-of-order for loop iteration
counter++;
console.log(counter2)
let keys = [1,2,6,6,1,6,[1],[1],10]
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
console.log("processing iteration: [", counter, "] index: [", i, "]")
if (keys[key] === false) {
break;
} else {
if (Array.isArray(keys[key])) {
let x = await doStuff(parseInt(key));
console.log(counter2)
// as suggested in an answer, I tried assigning the result 
// to a variable to create a closure, but nothing changes 
// in the output
doOtherStuff(parseInt(key));
delete keys[key];
}
}
}
}
</script>

最新更新