为什么不能直接在 map 中使用 await?

为什么不能直接在 map 中使用 await?

2025-03-07 18:58:0039 浏览656作者:dreamk前端开发
标签前端

写代码经常碰到这么个场景:手里有一组数据,要对每一项发请求拿结果。第一反应就是 mapasync/await,结果跑出来一看,拿到的不是结果数组,是一堆 Promise 对象。

问题在哪

map 的回调函数是同步的,它要求你返回一个具体的值。你在回调里加 async,函数就隐式返回一个 Promise,而 map 根本不等你这个 Promise resolve,直接把 Promise 塞进新数组里了。

所以你以为拿到的是 [结果1, 结果2, 结果3],实际拿到的是 [Promise, Promise, Promise]。这跟 map 的设计有关,它从骨子里就是同步的,不懂什么叫等待。

并发:用 Promise.all

最常用的解法就是把 map 包进 Promise.all 里。Promise.all 会把这些 Promise 并发跑起来,等它们全部 resolve,再返回结果数组。

async function processData(data) {
  const results = await Promise.all(
    data.map(async (item) => {
      const response = await fetch(`https://api.example.com/data/${item.id}`);
      return response.json();
    })
  );
  return results;
}
 
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
 
processData(data)
  .then((results) => console.log(results))
  .catch((error) => console.error(error));

这样写的好处是并发,三个请求同时发出去,总耗时约等于最慢的那一个,而不是三个加起来。大多数场景这么写就够了。

顺序:用 for...of

但有时候你不能并发。比如后端有限流,或者后一项的请求依赖前一项的结果,这时候就得老老实实按顺序来,换 for...of

async function processDataSequentially(data) {
  const results = [];
  for (const item of data) {
    const response = await fetch(`https://api.example.com/data/${item.id}`);
    results.push(await response.json());
  }
  return results;
}

for...of 是真能配合 await 用的,它会等前一个请求完事再发下一个。代价就是慢,n 个请求串着跑,总耗时是所有请求加起来。

顺序执行虽然慢,但调试好排查,而且不会因为并发太多把后端打挂。如果你的异步操作有副作用或者互相依赖,老老实实用这个。

封装个 asyncMap

项目里经常用到并发写法的话,可以自己封装一个 asyncMap,用起来顺手一点:

async function asyncMap(arr, fn) {
  return Promise.all(arr.map(fn));
}
 
// 用起来跟 map 一模一样,前面加个 await 就行
const results = await asyncMap(data, async (item) => {
  const res = await fetch(`https://api.example.com/data/${item.id}`);
  return res.json();
});

本质还是 Promise.all + map,就是包了一层,代码读起来顺一点。


搞清楚一件事就行:map 是同步的,await 是异步的,它俩硬凑一起只会给你一堆没兑现的 Promise。要么用 Promise.all 把这些 Promise 一起收了(并发),要么用 for...of 一个一个等(顺序)。想明白这点,怎么写都不会翻车。

评论区

0 条评论

还没有评论,欢迎成为第一个留言的人。