[译] 如何逃离 async/await 地狱

async/await 将咱们从回调地狱中解脱,但人们的滥用,致使了 async/await 地狱的诞生。javascript

本文将阐述什么是 async/await 地狱,以及逃离 async/await 地狱的几个方法。前端

什么是 async/await 地狱

进行 JavaScript 异步编程时,你们常常须要逐一编写多个复杂语句的代码,并都在调用语句前标注了 await。因为大多数状况下,一个语句并不依赖于前一个语句,可是你仍不得不等前一个语句完成,这会致使性能问题。java

一个 async/await 地狱示例

思考一下,若是你须要写一段脚本预订一个披萨和一杯饮料。脚本可能会是这样的:android

(async () => {
  const pizzaData = await getPizzaData()    // 异步调用
  const drinkData = await getDrinkData()    // 异步调用
  const chosenPizza = choosePizza()    // 同步调用
  const chosenDrink = chooseDrink()    // 同步调用
  await addPizzaToCart(chosenPizza)    // 异步调用
  await addDrinkToCart(chosenDrink)    // 异步调用
  orderItems()    // 异步调用
})()
复制代码

表面上看起来没什么问题,这段代码也能够执行。可是它并非一个好的实现,由于它没有考虑并发性。让咱们了解一下这段代码是怎么运行的,这样才能够肯定问题所在。ios

解释

咱们将这段代码包裹在一个异步的 IIFE 当即执行函数 中。准确的执行顺序以下:git

  1. 获取披萨列表。
  2. 获取饮料列表。
  3. 从列表中选择一份披萨。
  4. 从列表中选择一杯饮料。
  5. 将选中披萨加入购物车。
  6. 将选中饮料加入购物车。
  7. 将购物车内物品下单。

哪里出问题了?

如我以前所强调过的,全部语句都会逐一执行。此处并没有并发操做。仔细想一下:为何咱们要在获取披萨列表完成后才去获取饮料列表呢?两个列表应该一块儿获取。可是咱们在选择披萨时,确实须要在这以前已获取饮料列表。饮料同理。github

所以,咱们能够总结出,披萨相关的事务与饮料相关事务能够并发发生,可是披萨相关事务内部的独立步骤须要继发进行(逐一进行)。编程

另外一个糟糕实现的例子

这段代码将获取购物车内的东西,并发起一个请求下单。后端

async function orderItems() {
  const items = await getCartItems()    // 异步调用
  const noOfItems = items.length
  for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i])    // 异步调用
  }
}
复制代码

在这种状况下,for 循环须要等待 sendRequest() 函数完成后才能进行下一个迭代。事实上,咱们不须要等待。咱们想要尽快发送全部请求,而后等待全部请求执行完毕。数组

但愿如今你能够更理解 async/await 地狱是什么,以及它对你的程序性能影响有多么严重。如今,我想问你一个问题。

若是咱们忘了 await 关键字会怎样?

若是你在调用一个异步函数时忘了使用 await 关键字,该函数就会当即开始执行。这意味着 await 对于函数的执行来讲不是必需的。异步函数会返回一个 promise 对象,你能够稍后使用这个 promise。

(async () => {
  const value = doSomeAsyncTask()
  console.log(value) // 一个未完成的 promise
})()
复制代码

不使用 await 调用异步函数的另外一个后果是,编译器不知道你想等待这个函数执行完成。所以编译器将在异步任务完成以前就退出程序。所以咱们确实须要 await 关键字。

promise 有一个好玩的特性,你能够在一行代码中获得一个 promise 对象,在另外一行代码中获得这个 promise 的执行结果。这是逃离 async/await 地狱的关键。

(async () => {
  const promise = doSomeAsyncTask()
  const value = await promise
  console.log(value) // 实际的返回值
})()
复制代码

如你所见,doSomeAsyncTask() 返回了一个 promise 对象。此时 doSomeAsyncTask() 开始执行。咱们使用 await 关键字来获取 promise 对象的执行结果,并告诉 JavaScript 不要当即执行下一行代码,而是等待 promise 执行完成再执行下一行代码。

如何逃离 async/await 地狱?

你须要遵循如下步骤:

找到依赖其它语句执行结果的语句

在第一个示例中,咱们选择了一份披萨和一杯饮料。能够推断出在选择一份披萨前,咱们须要先得到全部披萨的列表。在将选择的披萨加入购物车以前,咱们须要先选择一份披萨。所以咱们能够说这三个步骤是互相依赖的。咱们不能在前一件事完成以前作下一件事。

可是若是把问题看得更普遍一些,咱们能够发现选披萨并不依赖选饮料,所以咱们能够并行选择。这方面,机器能够比咱们作的更好。

所以咱们已经发现有一些语句依赖于其它语句的执行,有些则不依赖。

将互相依赖的语句包裹在 async 函数中

如咱们所见,选择披萨包括了如获取披萨列表,选择披萨,将所选披萨加入购物车等依赖语句。咱们应该将这些语句包裹在一个 async 函数中。这样咱们获得了两个 async 函数,selectPizza()selectDrink()

并发执行 async 函数

而后咱们能够利用事件循环并发执行这些非阻塞 async 函数。有两种经常使用模式,分别是优先返回 promises 和使用Promise.all 方法

让咱们来修改一下示例

遵循如下三个步骤,将它们应用到咱们的示例中。

async function selectPizza() {
  const pizzaData = await getPizzaData()    // 异步调用
  const chosenPizza = choosePizza()    // 同步调用
  await addPizzaToCart(chosenPizza)    // 异步调用
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // 异步调用
  const chosenDrink = chooseDrink()    // 同步调用
  await addDrinkToCart(chosenDrink)    // 异步调用
}

(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // 异步调用
})()

// 我更喜欢这种方法

(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // 异步调用
})()
复制代码

如今咱们将语句分组到两个函数中。在函数内部,每一个语句依赖于前一个语句的执行。而后咱们并发执行两个函数 selectPizza()selectDrink()

在第二个例子中,咱们须要处理未知数量的 promise。解决这种状况很容易:建立一个数组,将 promise push 进去。而后使用 Promise.all() 咱们就能够并行等待全部的 promise 处理完毕。

async function orderItems() {
  const items = await getCartItems()    // 异步调用
  const noOfItems = items.length
  const promises = []
  for(var i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i])    // 异步调用
    promises.push(orderPromise)    // 同步调用
  }
  await Promise.all(promises)    // 异步调用
}
复制代码

但愿本文能够帮你提升 async/await 的基础水平并提高应用的性能。

若是喜欢本文,请点个喜欢。

也请分享到 Fb 和 Twitter。若是想获取文章更新,能够在 TwitterMedium 上关注我。有任何问题能够在评论中指出。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索