[译文]如何避开 async/await 地狱

原文地址 How to escape async/await hell数组

async/await把咱们从回调地狱中解放了出来,可是,人们也对其很有微词.由于随之而来致使了async/await地狱的诞生.promise

在这篇文章,我会试图解释什么是async/await地狱,另外我也会分享一些避开它们的方法.并发

什么是 async/await 地狱?

当咱们在编写JavaScript异步代码的时候,人们常常在一个接着一个的函数调用前面添加await关键字.这会致使性能问题,由于在一般状况下,一个语句的执行并不依赖前一个语句的执行,可是由于添加了await关键字,你仍旧须要等待前一个语句执行完才能执行一个语句.异步

一个 async/await 地狱的例子.

假设你写一段代码用来购买披萨和饮料,这段代码以下所示.async

(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
})()
复制代码

从表面上看,这段代码语法是正确的,而且可以运行.可是,这并非一个好的实现,由于它剔除了并发执行.接下来让咱们了解一下这段代码是作什么的,这样咱们更加明确其中的问题所在.函数

解释

咱们把这段代码包裹在了一个异步的当即执行函数里面.下面的事情会按次序发生:oop

  1. 得到披萨的列表.
  2. 得到饮料的列表.
  3. 从披萨列表中选择披萨.
  4. 从饮料列表中选择饮料.
  5. 把选择的披萨加入购物车
  6. 把选择的饮料加入购物车.
  7. 确认订单

错误在哪里?

就像我在前面提到的那样,全部的语句都是一行接着一行执行的,这里不存在并发执行的状况.让咱们仔细想一想,为何咱们在获取饮料列表以前须要等待披萨列表的返回?咱们应该尝试同时获取饮料和披萨的列表.然而,当咱们须要选择披萨的时候,咱们须要先获取披萨的列表.饮料也是如此.性能

所以咱们肯定,披萨相关的工做和饮料相关的工做可以同时执行,可是披萨相关的每一步工做须要按次序执行.(顺序执行)ui

另一个坏例子

这段JavaScript代码会得到购物车里面的物品,而后发送确认订单的请求.spa

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地狱以及它们对你程序的性能影响有多严重.如今,我想问你一个问题

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

若是你忘记在异步函数调用的前面添加await关键字,这时函数开始执行了,这意味着await并非函数执行的必要条件.这个异步函数会返回一个promise,这个promise咱们能够在以后使用.

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

复制代码

结果就是,编译器不知道你须要等待这个函数执行完成,所以编译器会在这个异步任务尚未完成的时候退出这个程序.所以咱们须要await关键字.

promise有一个有趣的性质是你能够在前面的代码获得这个promise, 而后在后面的代码中等待这个promise完成.这是从async/await地狱中解脱的关键.

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

正如你所见到的那样,doSomeAsyncTask()返回了一个promise.这个时候,doSomeAsyncTask()已经开始执行了.为了获得这个promise的结果值,咱们能够在这个promise前面添加await,JavaScript将会马上停在这里再也不执行下一行代码,直到得到了这个promise的返回值,再执行下一行代码.

如何逃离 async/await 地狱?

你应该跟随如下步骤来逃离async/await地狱.

找出全部以来其余语句执行的语句

在咱们第一个例子里面,咱们在选择披萨和饮料.于是咱们得出结论,在选择披萨以前,咱们须要得到披萨的列表.同时,在把披萨加入到购物车以前,咱们须要选择披萨.能够认为这三个步骤是互相依赖的,咱们不能在前一个步骤完成以前执行下一个任务. 可是,若是咱们拓宽一下眼界,就会发现选择披萨并不会依赖于选择饮料,咱们能够同时选择他们.这就是机器能作的比咱们更好的地方. 至此,咱们已经发现了一些语句依赖于其余的语句执行,可是另一些语句不依赖.

把相互依赖执行的语句整合在异步函数里面.

正如咱们所看到的,选择披萨须要几个互相依赖的语句,如得到披萨列表,选择其中一个披萨而后添加到购物车中.咱们应该把这些语句整合在一个异步函数里面.这样咱们将会获得两个异步函数,selectPizza()selectDrink()

并发的执行这些异步函数.

咱们将利用event loop的优点来并发执行这些非阻塞异步函数.为了达成这个目标,咱们经常使用的方法是先返回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
})()

// 我更喜欢下面这种实现.

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

复制代码

如今咱们已经把这些语句整合到两个函数中,在每个函数里面,每个语句的执行依赖于前一个函数的执行.而后咱们并发的执行selectPizza()selectDrink().

在第二个例子里面,咱们须要解决未知数量的promise.解决这种状况很是简单:咱们只须要建立一个数组而后把promise存入其中.而后使用Promise.all()方法,就可以并发的等待全部的promise返回结果.

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 function orderItems() {
  const items = await getCartItems()    // async call
  const promises = items.map((item) => sendRequest(item))
  await Promise.all(promises)    // async call
}
复制代码

我喜欢这篇文章可以帮助你脱离async/await基础使用者的行列,同时可以帮助你提升你的程序性能. 若是你喜欢这篇文章,但愿可以点赞并收藏.

相关文章
相关标签/搜索