原文地址:How to escape async/await hell
译文出自:夜色镇歌的我的博客javascript
async/await 把咱们从回调地狱中解救了出来,可是若是滥用就会掉进 async/await 地狱。java
本文中我会解释一下什么是 async/await 地狱,并会分享几个技巧去避免。编程
异步 Javascript 编程中,咱们一般会写许多 async 方法,而且使用 await
关键字去等待它,有不少时候下一行的执行并不依赖于上一行,可是咱们仍然使用了 await
去等待,因此可能会致使一些性能问题。数组
如何编写一个订购披萨和饮料的代码?它可能会像这样:promise
(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
包裹了起来,而后下面这些会依次执行。异步
正如我刚强调的,这些语句会依次执行,没有并发。仔细想一下,为啥我获取饮料菜单以前得先获取披萨菜单?这两份菜单我应该同时去获取。固然,选择披萨以前得先获取披萨菜单,这个规则一样适用于饮料。async
因此咱们能够得出结论,披萨相关的工做和饮料相关的工做能够并行进行,但涉及披萨相关工做的各个步骤须要按顺序进行(一步接着一步)。函数
这段代码会获取购物车中的购物项而且发出订购请求。性能
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,async 函数会执行而且返回一个 Promise,你能够稍后再去resolve。
(async () => { const value = doSomeAsyncTask() console.log(value) // an unresolved promise })()
另外一个后果是编译器不知道你想把函数彻底执行,因此编译器会退出程序而不完成异步函数,因此仍是须要使用 await 关键字
promises 一个有趣的特性就是你能够在一行代码中去获得 Promise ,而在另一行中去等待并 resolve,这是避免 async/await 地狱的关键之处。
(async () => { const promise = doSomeAsyncTask() const value = await promise console.log(value) // the actual value })()
正如你看到的,doSomeAsyncTask()
方法返回一个 Promise,调用的时候它已经开始执行了,为了获得他的解析值,咱们使用了 await 关键字,告诉编译器等待解析完毕再执行下一行。
你应该按照这些步骤来避免 async/await 地狱:
第一个例子中,咱们选择了一个披萨和一杯饮料。总结一下,选择披萨以前得先获取披萨菜单,加到购物车以前得先选好,这三个步骤都是相互依赖的,必须等待上一个步骤完成后才能进行下一步。
咱们选择饮料的时候并不依赖于选择披萨,因此选择披萨和饮料是能够并行执行的。这也是机器能比咱们作的更好的一件事。
正如你看到的,选择披萨的依赖有获取披萨菜单、选择、添加到购物车。因此咱们把这些依赖放在一个异步方法里,饮料同理,这也是为何咱们会有 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()
第二个例子中,咱们须要处理未知数量的 Promise
。处理这种状况很简单,咱们先把 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 } // Although I prefer it this way async function orderItems() { const items = await getCartItems() // async call const promises = items.map((item) => sendRequest(item)) await Promise.all(promises) // async call }
但愿本文能够引起你对 async/await 使用的思考,也但愿能帮助你提高程序的性能。