如何避开 async/await 地狱
栏目: JavaScript · 发布时间: 6年前
内容简介:原文地址在这篇文章,我会试图解释什么是
原文地址 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
基础使用者的行列,同时能够帮助你提高你的程序性能.
如果你喜欢这篇文章,希望能够点赞并收藏.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。