async/await forEach 和 Promise.all + map 有什么区别

2024-03-20

In 类似问题的接受答案 https://stackoverflow.com/a/37576787/12458473答案指出forEach调用只是抛出一个承诺然后退出。我认为情况应该是这样forEach回报undefined但是为什么下面的代码可以工作呢?

const networkFunction = (callback) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(callback());
    }, 200);
  });
};

(async () => {
  const numbers = [0, 1, 2];
  // works in parallel
  numbers.forEach(async (num) => {
    await networkFunction(() => {
      console.log("For Each Function: Hello");
    });
  });
})();

它并行工作,这是输出time node main.js # main.js contains only the mentioned code

❯ time node main.js
For Each Function: Hello
For Each Function: Hello
For Each Function: Hello

________________________________________________________
Executed in  365.63 millis    fish           external
   usr time  126.02 millis  964.00 micros  125.05 millis
   sys time   36.68 millis  618.00 micros   36.06 millis

O.P. 的问题是要求澄清另一个 StackOverflow 问题在这里找到 https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop/37576787#37576787。如需进一步阅读以及有关此一般主题的许多其他精彩答案,请查看链接。

对于只看到问题标题的 Google 员工

不要将 async/await 与 forEach 一起使用。要么使用for-of循环,或使用Promise.all() with array.map().

如果您对 Promise 和 async/await 有一般性的了解,那么 TL;DR 会介绍两者之间的差异promise.all() + array.map() and .forEach(),是不可能等待forEach()本身。是的,您可以并行运行任务.forEach()就像你可以的那样.map(),但您不能等待所有这些并行任务完成,然后在它们全部完成后执行某些操作。使用的全部要点.map()代替.forEach()这样你就可以得到一份承诺清单,用以下方式收集它们Promise.all(),然后等待整个事情发生。要明白我的意思,只需添加一个console.log('Finished')之后forEach(async () => ...),你会看到"finished"在一切完成运行之前注销.forEach()环形。我的建议是不要使用.forEach()使用异步逻辑(实际上没有理由使用.forEach()这些天不再这样了,正如我在下面进一步解释的那样)。

对于那些需要更深入了解的人,本答案的其余部分将深入探讨更多细节,首先对 Promise 进行简短回顾,然后深入解释这些方法的行为方式有何不同以及原因.forEach()当涉及到 async/await 时,它始终是较差的解决方案。

Promise 和 async/await 入门

出于本次讨论的目的,您只需记住承诺是一个特殊的对象,它承诺某些任务将在未来的某个时刻完成。您可以通过以下方式将侦听器附加到 Promise:.then(),在任务完成时收到通知,并接收解析值。

An asyncfunction 只是一个无论如何都会返回 Promise 的函数。即使你这样做async function doThing() { return 2 },它不会返回 2,而是返回一个立即解析为值 2 的 Promise。请注意,异步函数将始终立即返回 Promise,即使该函数需要很长时间才能运行。这就是为什么它被称为“承诺”,它承诺该函数最终将完成运行,如果您想在函数完成时收到通知,您可以向其添加一个事件侦听器,通过.then() or await.

await是一种特殊的语法,可让您暂停异步函数的执行,直到承诺解决。await只会影响其直接内部的功能。在幕后,await只是简单地向 Promise 添加一个特殊的事件监听器.then(),因此它可以知道 Promise 何时解析以及解析的值是什么。

async function fn1() {
  async function fn2() {
    await myPromise // This pauses execution of fn2(), not fn1()!
  }
  ...
}

async function fn1() {
  function fn2() {
    await myPromise // An error, because fn2() is not async.
  }
  ...
}

如果您能够很好地掌握这些原则,那么您应该能够理解接下来的部分。

for-of

A for-of循环允许您一个接一个地串行执行异步任务。例如:

const delays = [1000, 1400, 1200];

// A function that will return a
// promise that resolves after the specified
// amount of time.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

async function main() {
  console.log('start')
  for (const delay of delays) {
    await wait(delay)
    console.log('Finished waiting for the delay ' + delay)
  }
  console.log('finish')
}

main()

The await causes main()暂停指定的延迟,之后循环继续,console.log()执行,循环再次开始下一次迭代,开始新的延迟。

希望这个应该有点简单。

Promise.all() + array.map()

指某东西的用途Promise.all() and array.map()Together 可以让您有效地并行运行许多异步任务,例如,我们可以等待许多不同的延迟同时完成。

const delays = [1000, 1400, 1200];

// A function that will return a
// promise that resolves after the specified
// amount of time.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

async function main() {
  console.log('start')
  await Promise.all(delays.map(async delay => {
    await wait(delay)
    console.log('Finished waiting for the delay ' + delay)
  }))
  console.log('finish')
}

main()

如果您还记得我们关于 Promise 和 async/await 的快速入门知识,您就会记得await只影响其直接内部的函数,导致该函数暂停。在这种情况下,await from await wait(delay)不会造成main()像前面的示例一样暂停,相反,它将导致回调被传递到delays.map()暂停,因为这是它直接位于内部的函数。

所以,我们有delays.map()这将调用提供的回调,每个delay里面的delays大批。回调是异步的,因此它总是会返回一个承诺立即地。回调将开始使用不同的延迟参数执行,但不可避免地会遇到await wait(delay)行,暂停回调的执行。

因为回调为.map()返回一个承诺,delays.map()将返回一系列承诺,其中Promise.all()然后将接收它们,并将它们组合在一起形成一个超级承诺,当数组中的所有承诺都解决时,该超级承诺就会解决。现在我们await返回的超级承诺promise.all(). This await在里面main(),导致main()暂停,直到所有提供的承诺都得到解决。因此,我们在其中创建了一堆独立的异步任务main(),让他们都在自己的时间完成,然后暂停执行main()直到所有这些任务完成。

.forEach() 的问题

首先,你真的不需要使用forEach()为了任何东西。之前就出来了for-of循环做了,并且for-of简直比forEach()各方面。for-of可以针对任何可迭代运行,您可以使用break and continue和他们在一起,最重要的是对于这个话题,await将按预期工作for-of但不在forEach()。原因如下:

const delays = [1000, 1400, 1200];

// A function that will return a
// promise that resolves after the specified
// amount of time.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

async function main() {
  console.log('start')
  delays.forEach(async delay => {
    await wait(delay)
    console.log('Finished waiting for the delay ' + delay)
  })
  console.log('finish')
}

main()

首先,你会注意到.forEach()将导致任务并行运行而不是串行运行,就像.map()。这是因为await代替forEach()只影响回调,不影响main()。所以,当我们跑步时delays.forEach(),我们为每个调用这个异步函数delay in delays,启动一堆异步任务。问题是,没有什么会等待异步任务完成,实际上,不可能等待它们。异步回调每次被调用时都会返回 Promise,但与.map(), .forEach()完全忽略其回调的返回值。.forEach()收到承诺,然后简单地忽略它。这使得不可能将承诺组合在一起并且await他们都通过Promise.all()就像我们之前做的那样。正因为如此,你会注意到"finish"立即注销,因为我们从来没有造成main()等待这些承诺完成。这可能不是您想要的。


原问题的具体答案

(又名原始答案)

它之所以有效,是因为 for 循环仍然运行,并且您仍然启动一堆异步任务。问题是你没有在等他们。当然你在里面使用了await.forEach(),但这只会导致.forEach()回调等待,它不会暂停你的外部函数。如果你把一个console.log()在 async IIFE 结束时,您的 console.log() 将在所有请求完成之前立即触发。如果您使用 Promise.all() 代替,那么 console.log() 将在请求完成后触发。

破损版本:

const networkFunction = (callback) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(callback());
    }, 200);
  });
};

(async () => {
  const numbers = [0, 1, 2];
  // works in parallel
  numbers.forEach(async (num) => {
    await networkFunction(() => {
      console.log("For Each Function: Hello");
    });
  });
  console.log('All requests finished!')
})();

固定版本:

const networkFunction = (callback) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(callback());
    }, 200);
  });
};

(async () => {
  const numbers = [0, 1, 2];
  // works in parallel
  await Promise.all(numbers.map(async (num) => {
    await networkFunction(() => {
      console.log("For Each Function: Hello");
    });
  }));
  console.log('All requests finished!')
})();
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

async/await forEach 和 Promise.all + map 有什么区别 的相关文章

  • 动态速度计 javascript 或 jquery 插件

    我希望有动态ajax插件在页面上显示速度计 一个想法是我设置一个背景并旋转针 有人知道相关插件吗 这里有一些供您参考 http bernii github com gauge js http bernii github com gauge
  • 网站 YouTube 嵌入视频不断播放

    我正在使用 youtube 提供的 iframe 在我的网站上嵌入视频 我还使用了一个 css 弹出窗口 这是我从这个页面学到的http www pat burt com web development how to do a css po
  • 在网页上的文本框中键入内容时删除所有空格

    我如何在用户打字时即时删除输入到文本框中的空格 function var txt myTextbox var func function txt val txt val replace s g txt keyup func blur fun
  • 具有 Firebase (FCM) 推送通知的 Node js

    我正在使用 Node js 开发 REST api 并且有一个休息端点来发送 firebase 推送通知 我的代码如下 const bodyParser require body parser var cors require cors v
  • 如何使用 jest 模拟第三方库

    我正在开发一个node js应用程序使用nestjs我有一堂课叫LoggerService如下 export class LoggerService private logger Rollbar constructor this logge
  • Leaflet js虚构地图

    我是 Leaflet 的新手 我想了解如何创建完全交互式的虚构地图 我有一张图像想要转换为传单地图 该图像基本上像图表一样具有许多连接和点 我想首先将该图像转换为地图 能够将鼠标悬停在这些点上 突出显示它们并显示有关它们的信息 并且还可以在
  • 使用 CryptoJS 更改密钥 [重复]

    这个问题在这里已经有答案了 我正在使用 CryptoJS 来加密和解密文本 在这里 我只是获取消息并显示加密和解密消息 我使用DES算法进行加密和解密 这是我的 HTML 文件
  • 检查 touchend 是否在拖动后出现

    我有一些代码可以更改表的类 在手机上 有时表格对于屏幕来说太宽 用户将拖动 滚动来查看内容 但是 当他们触摸并拖动表格时 每次拖动都会触发 touchend 如何测试触摸端是否是触摸拖动的结果 我尝试跟踪dragstart和dragend
  • 是否可以使用 javascript 测试用户的浏览器/操作系统是否支持给定类型的链接?

    是否可以使用 javascript 或其他任何东西 测试用户的操作系统 浏览器是否支持给定的 url 方案 例如 大多数仅使用网络邮件的用户计算机上未设置 mailto 是否有可能以某种方式捕获单击 mailto 链接的尝试并弹出比浏览器错
  • 如果链接包含特定文本,jQuery 将类添加到 href

    我的网站上的列表中有一些动态填充的链接 这些链接链接到文件 是否可以使用 jQuery 查看文件名是否以 pdf 结尾 并在 href 或类似的链接文本以 mp3 结尾时添加一个类 例如 我的列表中有以下链接 文件1 pdf 歌曲1 mp3
  • 如何流式传输 OpenAI 的完成 API?

    我想流式传输结果通过 OpenAI 的 API 完成 https beta openai com docs api reference completions 该文档提到使用服务器发送的事件 https developer mozilla
  • 在d3.js中将2D形状转换为3D,并根据ANGULAR中的值调整高度

    我正在使用 d3 js v6 创建以下 2D 图表表示的 3D 图表 这个圆圈中有多个正方形 每个正方形都根据值分配了一种颜色 值越大 正方形越暗 现在我想将其转换为 3D 形状 其中当值变高时 只有特定正方形的高度会增加 因此结果在某种程
  • Typeahead.js substringMatcher 函数说明

    我只是在做一些研究Typeahead js这是一个非常酷的图书馆 感谢文档 我已经成功地获得了一个基本的示例 该文档也非常好 但是我试图弄清楚以下代码块实际上在做什么 var substringMatcher function strs r
  • 流星内存不足

    我正在使用流星来制作报废引擎 我必须执行一个 HTTP GET 请求 这会向我发送一个 xml 但这个 xml 大于 400 ko 我得到一个异常 内存不足 result Meteor http get http SomeUrl com 致
  • IE11不监听MSFullscreenChange事件

    我正在尝试使用 Bigscreen js 在 IE11 中使用全屏 但 IE11 不监听 MS FullscreenChange 事件 document addEventListener MSFullscreenChange functio
  • 如果数字小于 10,则显示前导零 [重复]

    这个问题在这里已经有答案了 可能的重复 JavaScript 相当于 printf string format https stackoverflow com questions 610406 javascript equivalent t
  • 如何获取 UIWebView 中元素的位置?

    我在 iPad 程序中加载了 html 的 UIWebView 通过使用 webkit column width 我将 html 分为几列 padding 0px height 1024px webkit column gap 0px we
  • 防止文本区域出现新行

    我正在开发聊天功能 使用 Vue 并使用文本区域作为输入 以便溢出换行 并且对于编写较长消息的用户来说更具可读性 不幸的是 当用户按下 Enter 键并提交时 光标会在提交之前移动到新行 从而使用户体验感觉不佳 关于如何使用普通 Javas
  • 单击列表时使用 bootstrap Dropdown 防止下拉菜单消失

    我正在使用使用引导下拉菜单 http twitter github com bootstrap javascript html dropdowns生成下拉菜单 我想防止点击菜单时菜单消失 我已经实现了以下代码 但它不起作用 知道如何修复它吗
  • 用于 C# XNA 的 Javascript(或类似)游戏脚本

    最近我准备用 XNA C 开发另一个游戏 上次我在 XNA C 中开发游戏时 遇到了必须向游戏中添加地图和可自定义数据的问题 每次我想添加新内容或更改游戏角色的某些值或其他内容时 我都必须重建整个游戏或其他内容 这可能需要相当长的时间 有没

随机推荐