别再以为 await 到处都一样:这只是“半个真相”

异步 JS 很“怪”。教程常这么教:函数前加 async,Promise 前放 await,完事儿。 但在循环 + 异步的组合里:并不是所有循环都懂得等待。

大多数前端以为:有了 await,代码哪里都能暂停。 抱歉——这话只对了一半。

await 的行为,会因为使用场景而剧烈变化。 尤其是:你选的循环方式,会彻底改变await 的效果。

这是我真希望 6 个月前就有人告诉我的事,省下我一堆 async/await 的坑。

核心误解:会“等”的不是所有地方

异步 JS 很“怪”。教程常这么教:函数前加 async,Promise 前放 await,完事儿。 但在循环 + 异步的组合里:并不是所有循环都懂得等待

有的直接无视你的 await; 有的全部并行(即便你不想); 还有的会默默给你整崩

这里是 await 的“坟场”:forEach

最常见、伤害最大的错误:**在 forEach 里用 await**。

async function fetchUserProfiles() {
  const userIds = [1, 2, 3, 4, 5]
  
  userIds.forEach(async (id) => {
    const profile = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
    const data = await profile.json()
    console.log(`User ${id}:`, data.name)
  })
  
  console.log('All done fetching users')
}

fetchUserProfiles()

运行看看:

  • “All done fetching users” 会立刻打印,在任何 fetch 完成之前。
  • 控制台顺序乱序:2、5、1、3、4……随缘。

原因forEach不关心 Promise。你的回调是 async,会返回 Promise,但 forEach直接忽略返回值调用完就走。 你也没法 await forEach,因为它不返回有用的东西

看起来像对的语法,逻辑也似乎“合理”,但——**forEach 天生不为 Promise 设计**。

map 与 Promise 的“隐形坑”

这段代码在 Review 里很常见,也很迷惑

async function getAllUserData() {
  const userIds = [1, 2, 3]
  
  const userData = await userIds.map(async (id) => {
    const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
    return response.json()
  })
  
  console.log(userData)
}

你以为能拿到用户对象数组?不,你会得到Promise 数组await userIds.map(...)并不会等待里面的异步完成;map 只会收集回调返回的 Promise

正确写法:先收集 Promise,再 Promise.all 一把梭。

async function getAllUserData() {
  const userIds = [1, 2, 3]
  
  const userData = await Promise.all(
    userIds.map(id => 
      fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then(res => res.json())
    )
  )
  
  console.log(userData)
}

不小心的“串行杀性能”

确实有场景需要严格顺序执行,这时用 for…of 很香:

async function processSequentially() {
  const ids = [1, 2, 3]
  
  for (const id of ids) {
    const result = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
    const data = await result.json()
    console.log(`Processed ${id}:`, data)
  }
  
  console.log('Done')
}

这段代码直观、可读、靠谱。 但要小心:纯串行意味着每个都要等上一个。 如果每次请求 1s,五次就5s只有在确实存在依赖/限速/必须有序时才用串行;否则你就白白慢了 5 倍。

为什么 for…of 会“真的等”

for…of 是语言级的循环,不是数组方法。 在 for…of 里用 await整个循环会在该处暂停,直到 Promise 解决:

const userIds = [1, 2, 3];

for (const id of userIds) {
  const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`); // 这里暂停
  const data = await response.json();
  console.log(data); // 等待后才继续下一轮
}

这与无法暂停的 forEach 本质不同。使用场景强顺序节流/限流结果必须按序

要速度,就上 Promise.all 并行

当任务互不依赖,且你追求吞吐

async function processInParallel() {
  const ids = [1, 2, 3, 4, 5]
  
  const promises = ids.map(id => 
    fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then(res => res.json())
  )
  
  const results = await Promise.all(promises)
  console.log('All results:', results)
}

如果每个耗时 1s,并行就是**~1s 完成(不考虑带宽/服务端限制)。顺序保持与原数组一致,即便完成顺序不同。注意:Promise.all失败即全部失败——任何一个 reject,整体直接 reject**,中间结果拿不到。

更韧性的并行:Promise.allSettled

想要“不管成功失败都收集”

async function fetchAllSources() {
  const sources = [
    'https://api1.com/data',
    'https://api2.com/data',
    'https://api3.com/data'
  ]
  
  const promises = sources.map(url => 
    fetch(url).then(res => res.json())
  )
  
  const results = await Promise.allSettled(promises)
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Source ${index} succeeded:`, result.value)
    } else {
      console.error(`Source ${index} failed:`, result.reason)
    }
  })
}

allSettled 会等所有 Promise 都 settle(resolve/reject 均可),然后给你完整战况

能不用就别用的:reduce + await

在 reduce 里玩 await,读起来绕、调试更绕:

const userIds = [1, 2, 3];

async function fetchUsers() {
  const users = await userIds.reduce(async (accPromise, id) => {
    const acc = await accPromise;
    const user = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
    return [...acc, user];
  }, Promise.resolve([]));
  
  console.log(users);
}

fetchUsers();

累加器从第二轮开始就是 Promise,你还得层层 await。 这通常比 for…of 或 Promise.all更难读也更慢。 除非你确实在构建“步步相依”的序列,否则别选它。

进阶:可控并发(批处理)

全串行太慢、全并发会被限流?中间态:分批并发

async function processInBatches(items, batchSize = 5) {
  const results = []
  
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize)
    const batchResults = await Promise.all(
      batch.map(item => 
        fetch(`https://pokeapi.co/api/v2/pokemon/${item}`).then(res => res.json())
      )
    )
    results.push(...batchResults)
  }
  
  return results
}

每批最多 5 个,请求并发完成后再进下一批:性能优于纯串行,风险低于无限并发,非常适合第三方 API 的并发上限。

最后的要点

下次你想在循环里敲上一个 await先停半秒,想清楚你要的到底是:

  • 强顺序for…of),
  • 极速并行Promise.all),
  • 韧性收集Promise.allSettled),
  • 还是限流批处理(分批 Promise.all)。

玩一玩这些模式,欢迎在评论里补充你的最佳实践

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值