为什么你的async/await在forEach中失效?一眼看穿问题所在
\n\n你是否曾经写过类似这样的代码,希望它能按顺序执行异步操作?
\n\nconst users = ['alice', 'bob', 'charlie'];\n\nusers.forEach(async (user) => {\n const data = await fetch(`https://api.example.com/users/${user}`);\n console.log(await data.json());\n});\n\nconsole.log('完成!');\n\n然后发现"完成!"在数据加载之前就输出了?很熟悉吧?你并不孤单。这是Stack Overflow上最热门的问题之一,拥有数千次浏览量。问题在于forEach是一个同步方法。它只是启动回调函数,但不会等待它们完成。你的async函数开始执行了,但forEach已经继续向前运行,就像一个不在乎其他人终点的短跑运动员。
在本文中,我们不仅会展示如何修复这个问题。我们还会深入分析为什么会这样,并为你提供三个可行的解决方案及示例。你将了解何时使用Promise.all,何时使用经典的for...of。让我们开始吧!
问题剖析:forEach与Promise
\n\n让我们深入了解一下。JavaScript中的forEach方法是这样实现的:
Array.prototype.forEach = function(callback) {\n for (let i = 0; i < this.length; i++) {\n callback(this[i], i, this);\n }\n};\n\n看到了吗?它只是为每个元素调用callback,但不返回任何内容。即使回调函数返回一个Promise,forEach也会忽略它。它不会等待,不会收集结果——它只是启动然后遗忘。
当你编写async (user) => { ... }时,你的函数返回一个Promise。但forEach不知道如何处理它。最终,所有请求都会并行启动(如果是fetch请求),但你无法等待它们完成。forEach之后的代码会立即执行。
实际示例:在实践中是什么样子
\n\n想象一下,你正在处理一个商品ID数组,需要从数据库中获取每个商品的价格:
\n\nconst productIds = [101, 102, 103];\n\nasync function getPrice(id) {\n // 模拟数据库请求\n return new Promise(resolve => {\n setTimeout(() => {\n resolve(`商品 ${id} 的价格: $${Math.floor(Math.random() * 100)}`);\n }, 1000);\n });\n}\n\nproductIds.forEach(async (id) => {\n const price = await getPrice(id);\n console.log(price);\n});\n\nconsole.log('所有价格已加载!'); // 立即执行,而不是3秒后\n\n控制台输出结果:
\n所有价格已加载!\n商品 101 的价格: $42\n商品 102 的价格: $78\n商品 103 的价格: $15\n\n你同意吧,这不是我们期望的结果。好消息是:有一些简单而优雅的解决方案。
\n\n解决方案一:for...of — 顺序执行
\n\n让async/await在循环中工作的最简单方法是使用for...of。与forEach不同,for...of原生支持async/await,因为你在代码块内部控制执行流程。
const productIds = [101, 102, 103];\n\nasync function getPrice(id) {\n return new Promise(resolve => {\n setTimeout(() => {\n resolve(`商品 ${id} 的价格: $${Math.floor(Math.random() * 100)}`);\n }, 1000);\n });\n}\n\nasync function loadAllPrices() {\n for (const id of productIds) {\n const price = await getPrice(id);\n console.log(price);\n }\n console.log('所有价格已加载!');\n}\n\nloadAllPrices();\n\n现在结果将是:
\n商品 101 的价格: $42\n(1秒后)\n商品 102 的价格: $78\n(1秒后)\n商品 103 的价格: $15\n(1秒后)\n所有价格已加载!\n\n优点:简单、可读性强、顺序可预测。
\n缺点:速度慢——每个请求都要等待上一个完成