如何避免async/await地狱

951 阅读6分钟
原文链接: www.zcfy.cc

async/await 让我们摆脱了回调地狱,但人们开始滥用它 - 这就导致async/await 地狱的诞生。

在本文中,我将尝试解释什么是async/await hell,我也会分享一些提示来避免它。

什么是async/await hell

在使用异步JavaScript时,人们通常会依次编写多个语句,并在函数调用之前进行等待。这会导致性能问题,因为一个语句多次不依赖于前一个语句 - 但是您仍然必须等待前一个语句完成。

举个关于async/await hell的例子

假如你是写了一个脚本来订购比萨饼和饮料,那这个脚本可能是这个样子的:

(async () => {
  const pizzaData = await getPizzaData()    // async call
  const drinkData = await getDrinkData()    // async call
  const chosenPizza = choosePizza()    // sync call
  const chosenDrink = chooseDrink()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
  await addDrinkToCart(chosenDrink)    // async call
  orderItems()    // async call
})()

表面上它看起来是正确的,它确实有效。但是这不是一个好的实现,因为他并没有考虑到并发性。让我们了解它在做什么,以便我们能够确定问题

说明:

我们将我们的代码封装在一个 async IIFE. (IIFE(立即调用函数表达式)是一个JavaScript函数,它在定义时就运行。)那么它将按照以下顺序执行:

  1. Get the list of pizzas.(获取一个披萨的列表)
  2. Get the list of drinks.(获取一个饮料的列表)
  3. Choose one pizza from the list.(在这个列表中选择一个披萨)
  4. Choose one drink from the list.(在这个列表中选择一个饮料)
  5. Add the chosen pizza to the cart.(将选中的比萨加入购物车)
  6. Add the chosen drink to the cart.(将选中的饮料加入购物车)
  7. Order the items in the cart.(订购购物车中的物品)

那么现在有个什么问题了 ?

正如我先前强调的那样,所有这些陈述都是一一执行的。这里没有并发。仔细想想:为什么我们在尝试获得饮料清单之前等待获得比萨饼列表?我们应该试着将这两个列表放在一起。然而,当我们需要选择比萨时,我们确实需要事先得到比萨饼的名单。这同样适用于饮料。

所以我们可以得出结论,披萨相关的工作和饮料相关的工作可以并行进行,但涉及披萨相关工作的各个步骤需要按顺序(逐个)进行。

另一个糟糕的实施例子

此JavaScript代码将获取购物车中的商品并发出订购请求。

async function orderItems() {
  const items = await getCartItems()    // async call
  const noOfItems = items.length
  for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i])    // async call
  }
}

在这种情况下,for循环必须等待sendRequest()函数完成后才能继续下一次迭代。但是,我们并不需要等待。我们希望尽快发送所有请求,然后我们可以等待所有请求完成。

我希望现在你越来越接近理解什么是async/await hell,以及它对程序性能的影响有多严重。现在我想问你一个问题。

如果我们忘记了await关键字会怎么样 ?

如果在调用异步函数时忘记使用await,则该函数开始执行。这意味着执行该功能不需要等待。异步函数将返回一个promise,您可以稍后使用。

(async () => {
  const value = doSomeAsyncTask()
  console.log(value) // an unresolved promise
})()

另一个结果是编译器不会知道你想等待函数完全执行。因此,编译器将退出程序而不完成异步任务。所以我们确实需要await关键字。

promises的一个有趣属性是,你可以在一行中得到promise,并等待它在另一行中解决。这是避免async/await hel的关键。

(async () => {
  const promise = doSomeAsyncTask()
  const value = await promise
  console.log(value) // the actual value
})()

正如你所看到的,doSomeAsyncTask()正在返回一个promises。此时doSomeAsyncTask()已经开始执行。为了得到promise的解析值,我们使用await关键字,它会告诉JavaScript不要立即执行下一行,而是等待promises解决,然后执行下一行。

如何摆脱 async/await地狱 ?

你应该按照这些步骤来避免async/await地狱。

查找依赖于其他语句执行的语句

在我们的第一个例子中,我们选择了一个披萨和一杯饮料。我们的结论是,在选择比萨饼之前,我们需要有比萨饼的名单。在将比萨加入购物车之前,我们需要选择比萨饼。所以我们可以说这三个步骤取决于对方。在完成前一件事之前我们不能做一件事。

但如果我们更广泛地来看,我们发现选择比萨不依赖于选择饮料,所以我们可以并行选择它们。

因此我们发现了一些依赖于其他语句执行的语句,有些则没有。

Group-dependent statements in async functions将需要async的函数封装在一起

将需要async的函数封装在一起

正如我们所看到的,选择比萨包括从属陈述,比如获得比萨饼列表,选择一个,然后将选择的比​​萨加入购物车。我们应该将这些语句分组为异步函数。这样我们就得到了两个异步函数selectPizza()和selectDrink()。

同时执行这些异步功能

然后我们利用事件循环同时运行这些异步非阻塞函数。这样做的两种常见模式是早日返回Promise和Promise.all方法。

我们来修复这些例子

遵循这三个步骤,让我们将它们应用于我们的示例

async function selectPizza() {
  const pizzaData = await getPizzaData()    // async call
  const chosenPizza = choosePizza()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // async call
  const chosenDrink = chooseDrink()    // sync call
  await addDrinkToCart(chosenDrink)    // async call
}

(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // async call
})()

// Although I prefer it this way 

(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // async call
})()

现在我们将这些语句分成两个函数。在函数内部,每个语句都依赖于前一个语句的执行。然后我们同时执行函数selectPizza()和selectDrink()。

在第二个例子中,我们需要处理未知数量的promises。处理这种情况非常简单:我们只需创建一个数组并在其中实现承诺。然后使用Promise.all()我们同时等待所有的承诺解决。

async function orderItems() {
  const items = await getCartItems()    // async call
  const noOfItems = items.length
  const promises = []
  for(var i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i])    // async call
    promises.push(orderPromise)    // sync call
  }
  await Promise.all(promises)    // async call
}

我希望这篇文章能够帮助您超越async / await的基础知识,并帮助您提高应用程序的性能。

如果你喜欢这篇文章,请点赞。提示 - 你可以点50次! 也请分享Fb和Twitter。如果您想获得更新,请在 TwitterMedium上关注我。如果有什么不清楚或者你想指出什么,请在下面评论。