async/await把我们从回调地狱中解放了出来,但是随之而来的是大家对它的滥用,导致了async/await地狱的诞生。
在这篇文章里我将会解释什么是async/await地狱以及分享一些方法去避开它。
什么是async/await地狱?
当我们在编写JavaScript异步代码的时候,常常会在一个接着一个函数调用前添加await关键字,影响了函数的调用。因为在一般情况下,下一个函数的调用并不依赖于前一个函数的调用,但是正是因为添加了await关键字,我们仍需要等待前一个函数调用完毕才能调用下一个函数。
async/await地狱举例(1)
假设我们要写一段代码用于购买披萨和饮料,这段代码如下所示:
从表面上看这段代码没有问题并且能够运行,但是这不是一个好的方法因为没有考虑到并发问题。接下来让我们仔细分析一下这段代码然后明确其中问题所在。
解析
我们把这段代码包裹在了一个异步立即执行函数里,下列事件会依次发生:
- 获得披萨的列表.
- 获得饮料的列表.
- 在披萨列表中选择披萨.
- 在饮料列表中选择饮料.
- 把选择的披萨加入购物车
- 把选择的饮料加入购物车
- 确认订单
问题在哪里?
就像我在前面提到的那样,所有的代码都是一行接着一行执行的,这里不存在并发执行的情况。让我们仔细想想,为什么我们在获取饮料列表之前需要等待披萨列表的返回?我们应该尝试同时获取饮料和披萨的列表。然而,当我们需要选择披萨的时候,我们需要先获取披萨的列表。饮料也是如此。
因此我们可以得出结论:披萨相关的工作和饮料相关的工作能够同时执行,但是披萨相关的每一步工作需要按次序执行。
错误示范例子(2)
下面这段JavaScript代码会获取购物车里面的物品,然后发送确认订单的请求。
在这种情况下,for循环在执行下一轮循环之前需要等待当前的sendRequest()函数执行完成。然而,实际上我们不需要等待,我们希望尽可能快的发送所有请求然后等待他们都完成执行。
现在,我希望大家能够清晰的理解什么是async/await地狱以及它们对程序的性能影响的严重性。现在,我要问一个问题。
如果我们忘记了await关键字会怎样?
如果你忘记在异步函数调用的前面添加await关键字,这时函数开始执行了,这意味着await并不是函数执行的必要条件。这个异步函数会返回一个promise,这个promise我们可以在之后使用。
另一个后果就是编译程序不知道你需要等待这个函数执行完成,因此编译程序会在这个异步任务还没有完成的时候退出这个程序,所以我们需要await关键字。
promise有一个有趣的性质是:你可以在前面的代码得到这个promise, 然后在后面的代码中等待这个promise的完成。这是从async/await地狱中解脱的关键。
正如你所见到的那样,doSomeAsyncTask()
返回了一个promise。这个时候,doSomeAsyncTask()已经开始执行了。为了得到这个promise的结果值,我们可以在这个promise前面添加await,JavaScript将会立刻停在这里不再执行下一行代码,直到获得了这个promise的返回值,再执行下一行代码。
如何避开 async/await 地狱?
依照以下步骤来逃离async/await地狱。
找出所有依赖其他代码来执行的代码
在我们第一个例子里面,我们在选择披萨和饮料。因而我们得出结论:在选择披萨之前,我们需要获得披萨的列表;在把披萨加入到购物车之前,我们需要选择披萨。我们可以认定这三个步骤是互相依赖的,我们无法在前一个步骤完成之前执行下一个任务。但是,如果我们退一步来看,就会发现选择披萨并不依赖于选择饮料,我们可以同时对他们进行选择。在这一件事情上,机器能比人类做得更好。
所以我们已经发现了一些语句依赖于其他的语句执行而另外一些语句并不依赖于其他。
把相互依赖执行的语句整合在异步函数里面
正如我们所看到的,选择披萨需要以下几个互相依赖的语句:获得披萨列表, 选择其中一个披萨以及添加到购物车中。我们应该把这些语句整合在一个异步函数里面。这样我们将会得到两个异步函数,selectPizza()和
selectDrink()。
并发的执行这些异步函数
我们将利用event loop的优势来并发的执行这些非阻塞异步函数。常用的一个方法是先返回promise然后使用Promise.all方法。
改正错误示范的例子
根据前面提到的三个步骤,我们把他们运用的我们的例子中。
现在,我们已经把这些代码整合到两个函数中。在每一个函数里面,每一条代码的执行依赖于前一条代码的执行。然后我们并发的执行selectPizza()
和selectDrink()
。
对于例2,我们需要解决未知数量的promise,解决这种情况非常简单:我们只需要创建一个数组然后把promise存入其中,然后使用Promise.all()
方法,就能够并发的等待所有的promise返回结果。
希望本文能帮助您看透async/await,并帮助您改进应用程序的性能。
❤️ 看之后
- 点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
- 关注公众号「新前端社区」,号享受文章首发体验!每周重点攻克一个前端技术难点。