Array.forEach是异步的吗?

8
根据 JavaScript、Node.js:Array.forEach 是否异步?,Array.forEach 是同步的。然而,在我下面的代码中:
function wait5() {
    return new Promise(resolve => 
    setTimeout(resolve, 5000));
}

async function main() {
   console.log("Start");
   [1,2].forEach(async (e) => {
      const d = await wait5().then(()=> console.log("5s later") )
   })
   console.log("This should come last!");
}

main();

输出结果为:
Start
This should come last!
5s later
5s later

连续两个"5秒后"为什么会这样呢?

如果使用普通的for循环:

async function main() {
   console.log("Start");
   for (let i=0;i<2;i++) {
      const d = await wait5().then(()=> console.log("5s later") )
   }
   console.log("This should come last!");
}

如果结果是我想要的,那么就是这样:
Start
5s later
5s later
This should come last!

8
.forEach() 机制本身不执行任何异步工作。但是,传递给回调函数的代码可以自由地执行任何操作。需要注意的是,这并不改变 .forEach() 的同步特性。 - Pointy
1
这里的关键是await - zero298
1
这与 forEach 没有任何关系,而与声明回调函数为 async[1,2].forEach(async (e) => 有关。 - gforce301
请注意,没有任何东西会关注从每个对.forEach()回调的调用返回的Promise。 - Pointy
这是你关于 JavaScript 异步性的第二个问题。我非常赞同,继续提出好问题。但是,是否有一个更好的核心问题可以问,比如“在 JS 中异步性实际上意味着什么?”或者“事件循环是如何工作的?”?我不确定如何更好地帮助你。 - zero298
2个回答

10

forEach是同步的。但是,您特定的回调函数不是。因此,forEach会同步调用您的函数,在数组中的每个条目上启动其工作。稍后,开始的工作异步完成,远远晚于forEach返回。

问题在于您的回调是async,而不是forEach是异步的。

通常情况下,当您使用像forEach这样不处理返回值(或不期望promise作为返回值)的API时:

  1. 不要传递一个async函数,或者
  2. 确保在函数本身内部处理错误

否则,如果函数出现问题,您将得到未处理的错误。

当然也可以:

  1. async函数中使用try/catch块来捕获、处理/报告函数本身内部的错误。

“await”调用不会阻止forEach迭代继续进行,直到结果返回吗? - Old Geezer
@OldGeezer - 不行。await 暂停回调函数,此时回调函数返回其 Promise。forEach 继续执行。你不能将同步函数变成异步函数。 - T.J. Crowder
1
我仍然感到困惑。为什么普通的 for 循环(请参见更新的详细信息)行为不同呢? - Old Geezer
1
@OldGeezer - 很好的问题。因为 async 函数中的 for 循环被修改了,这是由于它是一个 async 函数;修改基本流程控制结构是 async 函数所做的。forEach 并没有以那种方式进行修改。(当添加 async 函数时,它本来可以被修改,因为它是内置函数。但那会变得更加复杂和令人困惑。因此,只有 语法 而不是 API 函数被 async 修改。) - T.J. Crowder

1
似乎你正在一个不太关心这种事情的调用者(forEach)中声明一个async函数。声明一个async函数使其类似于Promise,但只有在采取行动时才有用。
如果你需要一个支持Promise的forEach,那么你可以实现它,不过像Bluebird这样的库中已经有了Promise.each

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