如何避开 async/await 地狱
栏目: JavaScript · 发布时间: 7年前
内容简介:原文地址在这篇文章,我会试图解释什么是
原文地址 How to escape async/await hell
async/await
把我们从回调地狱中解放了出来,但是,人们也对其颇有微词.因为随之而来导致了 async/await
地狱的诞生.
在这篇文章,我会试图解释什么是 async/await
地狱,另外我也会分享一些避开它们的方法.
什么是 async/await 地狱?
当我们在编写JavaScript异步代码的时候,人们经常在一个接着一个的函数调用前面添加 await
关键字.这会导致性能问题,因为在通常情况下,一个语句的执行并不依赖前一个语句的执行,但是因为添加了 await
关键字,你仍旧需要等待前一个语句执行完才能执行一个语句.
一个 async/await 地狱的例子.
假设你写一段代码用来购买披萨和饮料,这段代码如下所示.
(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
})()
复制代码
从表面上看,这段代码语法是正确的,并且能够运行.但是,这并不是一个好的实现,因为它剔除了并发执行.接下来让我们了解一下这段代码是做什么的,这样我们更加明确其中的问题所在.
解释
我们把这段代码包裹在了一个异步的立即执行函数里面.下面的事情会按次序发生:
- 获得披萨的列表.
- 获得饮料的列表.
- 从披萨列表中选择披萨.
- 从饮料列表中选择饮料.
- 把选择的披萨加入购物车
- 把选择的饮料加入购物车.
- 确认订单
错误在哪里?
就像我在前面提到的那样,所有的语句都是一行接着一行执行的,这里不存在并发执行的情况.让我们仔细想想,为什么我们在获取饮料列表之前需要等待披萨列表的返回?我们应该尝试同时获取饮料和披萨的列表.然而,当我们需要选择披萨的时候,我们需要先获取披萨的列表.饮料也是如此.
因此我们确定,披萨相关的工作和饮料相关的工作能够同时执行,但是披萨相关的每一步工作需要按次序执行.(顺序执行)
另外一个坏例子
这段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
地狱以及它们对你程序的性能影响有多严重.现在,我想问你一个问题
如果我们忘记了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
基础使用者的行列,同时能够帮助你提高你的程序性能.
如果你喜欢这篇文章,希望能够点赞并收藏.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing for Emotion
Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00
Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!