Async/await 的危险

2025-05-24

Async/await 的危险

在为一个大型应用程序的重写提供几个月的咨询之后,我意识到async/await实际上已经用于大多数异步操作,而并行执行似乎并不适用。例如,请考虑以下 Vue 代码片段:

 async initStore(query) {
    await this.getConfig();
    await this.getUser();
    await this.checkRussianContext(query);

    await this.getBasket(this.$store.state.config.selectedCurrency),

    await this.$store.dispatch('options/fetchOptions', {
      basket : this.$store.state.basket,
    });
 },
Enter fullscreen mode Exit fullscreen mode

这里,每一行代码都会在其前一行完成时执行。这意味着getUser它会等待getConfig数据获取完成后再执行。

看到这段代码时,我想到以下几点:

  • 如果一行不需要前一行的数据怎么办?为什么要阻止它的执行并降低应用程序的速度?
  • 我们可以使用类似的东西并行运行不相关的方法吗Promise.all
  • 相关方法可能应该使用then块来避免阻塞其余方法

本文的目的是通过向您展示在某些情况下默认使用/会对性能和用户体验产生巨大影响来帮助您发现这种代码异味。asyncawait

不相关的查询应该并行执行

让我们看一些具体的数据,好吗?

以下是我们将要分析的代码片段:

const getUserData = async () => {
  // Get a random dog as our user's avatar
  const res = await fetch('https://dog.ceo/api/breeds/image/random')
  const { message } = await res.json()

  // Get our user's general data
  const user = await fetch('https://randomuser.me/api/')
  const { results } = await user.json()

  // ...
}
Enter fullscreen mode Exit fullscreen mode

在快速 3G 上运行此代码片段 100 次(使用 Chrome 的开发工具),平均执行时间为1231.10ms

但是,既然第二个查询不需要第一个查询的结果,为什么要阻止它呢?让我们将代码更改为以下内容,并重新运行 100 次。

const getUserDataFaster = async () => {
  // Execute both requests in parallel
  const [res, user] = await Promise.all([
    fetch('https://dog.ceo/api/breeds/image/random'), 
    fetch('https://randomuser.me/api/')
  ])
  const [{ message }, { results }] = await Promise.all([res.json(), user.json()])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

现在,平均执行时间为612.50ms,是两个查询依次执行所需时间的一半。

重点是:如果您可以并行执行耗时的查询,那就这样做。

在这个codepen上亲自尝试一下

不相关的代码不应该等待

让我们以我的第一个例子为例,但稍加改动:

 async initStore(query) {
   await Promise.all([
     this.getConfig(),
     this.getUser(),
     this.checkRussianContext(query)
   ])

   await this.getBasket(this.$store.state.config.selectedCurrency),

   await this.$store.dispatch('options/fetchOptions', {
     basket : this.$store.state.basket,
   });

   await initBooking()
 },
Enter fullscreen mode Exit fullscreen mode

这里,前三个请求是并行执行的,而接下来的请求依赖于事先获取的数据,因此会在之后执行。虽然这段代码存在问题,但你发现了吗?

可怜的小家伙initBooking必须等待getBasket和都fetchOptions完成后才能执行,即使这与它们要获取的数据无关。一个简单的解决方案是用一个简单的区块

来交易awaitthen

  async initStore(query) {
    await Promise.all([
      this.getConfig(),
      this.getUser(),
      this.checkRussianContext(query)
    ])

    this.getBasket(this.$store.state.config.selectedCurrency).then(async () => {
      await this.$store.dispatch('options/fetchOptions', {
        basket : this.$store.state.basket,
      });
    })   

   await initBooking()
 },
Enter fullscreen mode Exit fullscreen mode

这样,和getBasketinitBooking同时执行。

想亲眼看看吗?查看这个codepen,它演示了我的示例。

我将就此结束本文,以免向您提供过多的例子,但您现在应该已经了解其要点了。

async/await是 Javascript 语言的精彩补充,但我希望您现在问自己,它们是否适合您正在使用的特定方法,更重要的是:您的某些查询是否可以并行执行。

感谢您的阅读,如果您在 Twitter 上关注我@christo_kade我会很高兴的,这样我们就可以分享我们对awaits❤️ 的相互怀疑

文章来源:https://dev.to/christopherkade/the-dangers-of-async-await-3p5g
PREV
值得了解的不常见 HTML 标签 🧩 总结
NEXT
Nuxt.js 备忘单