为什么使用 for...of 循环会等待 Promise 解决而 forEach() 方法却不会?

5

我在理解for...of.forEach()在处理Promises时的区别方面遇到了困难。

使用以下代码片段:

const allSettled = async arr => {
    const data = []

    for (const promise of arr) {
        try {
            const result = await promise;
            data.push(result);
        } catch (e) {
            data.push(e)
        }
    }

    return data
}

我遍历数组中的每个Promise,等待其完成并将结果推入data。它是按顺序执行的。

如果我有以下代码片段:

const badAllSettled = arr => {
    const data = []

    arr.forEach(async promise => {
        try {
            const result = await promise;
            data.push(result);
        } catch (e) {
            data.push(e)
        }
    })

    return data
}

我得到了一个空数组(因为forEach不等待Promise完成)。

据我所知,for...of适用于可迭代对象,因此它可能会暂停并等待await返回。但是这个流程是如何逐步进行的,我不太明白。

谢谢!


.forEach() 不支持 Promise。 - Pointy
2
for ... of 是控制流,受 await 影响(就像其他循环、if语句等一样)。forEach 只是一个方法调用,而 await 仅使 async 函数等待,但不会影响 badAllSettled - Bergi
3个回答

4

.forEach()的实现方式如下:

Array.prototype.forEach = function forEach(fn) {
  for (let i = 0; i < this.length; i++)
    fn(this[i], i, this);
}

可以看到,调用 .forEach() 时,它只是同步地多次调用回调函数。然而,如果回调函数是一个异步函数,它不会被等待。

相反,当在异步函数中发现 await 关键字时,该函数内的所有代码执行都会暂停,直到 Promise 解决,这意味着任何控制流程(包括 for ... of 循环)也会暂停。

以下代码的行为类似于 .forEach(),因为它调用了异步函数,该函数等待 Promise 而不等待函数返回的 Promise:

const allSettled = async arr => {
    const data = []

    for (const promise of arr) {
        (async () => {
            try {
                 const result = await promise;
                 data.push(result);
            } catch (e) {
                data.push(e)
            }
        )();
    }

    return data
}

2

for...of不会等待,await关键字才会进行等待。这里有一个awaitconst result = await promise;

forEach的内部不会await传递给forEach(此处)的函数返回的promise。(在生成该promise的函数中使用await是无关紧要的)。


1
for (const item of array) {
  // do something
}

对于数组中的每个元素,它都会执行“something”的操作。如果你在内部使用await,它将等待该操作完成后再继续执行代码的其余部分。

array.forEach(callback)

同步调用数组中每个元素的回调函数。如果您的回调函数是异步的,它将调用它,然后立即调用下一个回调函数。 .forEach 在调用下一个回调函数之前不会等待每个回调函数解析出结果。


请注意,如果您希望您的承诺并行运行,可能是为了提高性能,您可能想使用Promise.all
// runs every fetch sequentially
// if each of them takes 1s, this will take 3s to complete
const results = []
for (const item of [1,2,3]) {
  results.push(await fetchSomething(item))
}

// runs every fetch in parrallel
// if each of them takes 1s, this will take 1s to complete
const promises = [1,2,3].map(item => fetchSomething(item))
const results = await Promise.all(promises)

感谢您的解释!Promise.all 不容忍拒绝,因此我有这个“有点”allSettled polyfill。 另外,我尝试了常规的 for 循环,它也可以正常工作。 - Nikita Shchypyplov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接