Async/await 和 Array.map:当最好的朋友变成敌人
你是否曾经写过类似 const results = await array.map(async (item) => {...}) 这样的代码,结果却得到了一个 Promise 数组,而不是预期的数据?如果是这样——你并不孤单。这个问题在 Stack Overflow 上获得了数万次浏览,而踩过这个坑的开发人员数量已达数百万。
在本文中,我们将分析在 TypeScript 中将 async/await 与 Array.map 结合使用时最常见的 5 个错误。更重要的是,我将展示如何避免它们。系好安全带——过程会有些痛苦,但会很有收获。
错误 #1:忘记使用 Promise.all(最常见)
最常见的错误是认为 map 内部的 await 会神奇地等待所有 Promise 完成。剧透:不,它不会。
问题
// ❌ 这样不行const users = [1, 2, 3, 4, 5];const userData = await users.map(async (id) => { const response = await fetch(`/api/users/${id}`); return response.json();});// userData 是 Promise[],而不是 UserData[]!console.log(userData); // [Promise, Promise, Promise, Promise, Promise]为什么会这样?
Array.map 是同步的。它不会等待异步回调完成。每次调用 async 函数都会返回一个 Promise,而 map 只是将它们收集到一个数组中。map 前面的 await 只等待 map 本身完成,而不是它的回调。
解决方案
// ✅ 正确做法:用 Promise.all 包装const users = [1, 2, 3, 4, 5];const userData = await Promise.all( users.map(async (id) => { const response = await fetch(`/api/users/${id}`); return response.json(); }));// userData 是 UserData[]!🎉console.log(userData);错误 #2:所有 Promise 并行执行(但需要顺序执行)
Promise.all 会并行启动所有 Promise。这对性能来说很棒,但有时我们需要严格地一个接一个地执行请求。例如,当使用限制请求数量的 API 时,或者当每个后续请求依赖于前一个请求时。
问题
// ❌ 所有请求同时发出const ids = [1, 2, 3, 4, 5];const results = await Promise.all( ids.map(async (id) => { // 同时向 API 发出 5 个请求——可能会触发速率限制 return await api.processItem(id); }));解决方案:顺序执行
// ✅ 使用 for...of 配合 awaitconst ids = [1, 2, 3, 4, 5];const results: ItemData[] = [];
for (const id of ids) { const result = await api.processItem(id); results.push(result);}// results — 按顺序处理的数据数组另一种选择是使用带有 Promise 累加器的 Array.reduce:
// ✅ 也可以工作,但可读性较差const ids = [1, 2, 3, 4, 5];const results = await ids.reduce(async (accPromise, id) => { const acc = await accPromise; const result = await api.processItem(id); return [...acc, result];}, Promise.resolve([] as ItemData[]));错误 #3:TypeScript 中的类型错误
TypeScript 可能不会立即理解你在异步 map 中做了什么。特别是当你使用 Promise.all 而没有显式指定类型时。
问题
// ❌ TypeScript 报错或推断出错误的类型const ids = [1, 2, 3];const results = await Promise.all( ids.map(async (id) => { const data = await fetchData(id); return data.name; // TypeScript 可能认为这是 Promise<string> }));// results 的类型是 unknown 或 Promise<string>[]解决方案:显式类型注解
// ✅ 显式指定结果类型