为何forEach中catch块内的await无法实现串行执行?
咱们先从你给出的代码现象说起:processForLoop能按顺序逐个处理元素,完成一个再开始下一个;但processForEach却会一次性启动所有处理任务,最后同时完成。这背后的原因,得从forEach的设计逻辑和async/await的工作方式来拆解。
1. forEach根本不“等待”异步回调
forEach的核心逻辑是遍历数组时立即执行每个回调函数,它完全不关心回调函数是不是异步的,也不会跟踪回调返回的Promise。哪怕你给它传一个async函数,forEach也只是把这个函数当作普通函数调用,调用完就立刻跳到下一个元素,不会停下来等这个异步函数的Promise resolve。
看你的processForEach函数,其实写法还有个小问题:
// 错误写法:这里的await是多余的,而且forEach不会处理它 items.forEach(await tryToProcess); // 哪怕改成正确的async回调,也依然不会等待 items.forEach(async item => await tryToProcess(item));
哪怕改成上面的正确回调写法,forEach还是会在瞬间把所有async回调都调用一遍,启动所有异步任务——这就是为什么你会先看到所有Processing xxx打印出来,3秒后所有Resolved xxx一起出现的原因:三个resolveAfter3Seconds几乎是同时开始计时的。
2. 对比for循环:它是真的会“等”
而processForLoop里的for循环就不一样了:每次迭代里的await会暂停整个循环的执行,直到当前tryToProcess(item)返回的Promise完全resolve(也就是里面的process报错后,resolveAfter3Seconds完成),才会进入下一次迭代。
这种“等待上一个完成再开始下一个”的逻辑,刚好符合你期望的串行执行行为。
3. 怎么让forEach实现串行异步?
如果你非要用forEach风格的写法实现串行,其实可以用Array.reduce来模拟:
async function processForEachSerial(items) { await items.reduce(async (prevPromise, item) => { // 先等待上一个异步任务完成 await prevPromise; // 再执行当前的异步任务 await tryToProcess(item); }, Promise.resolve()); }
不过说实话,这种写法不如直接用for循环或for...of直观,后者是处理串行异步任务的首选方案。
总结一下
forEach是为同步遍历设计的,对异步回调的Promise没有任何处理逻辑,不会等待它们完成;for/for...of循环配合await,能自然实现异步任务的串行执行;- 如果需要并行执行所有异步任务,可以用
Promise.all(但要注意统一处理错误)。
内容的提问来源于stack exchange,提问作者Parabolord

