async/await把咱們從回調地獄中解放了出來,可是隨之而來的是你們對它的濫用,致使了async/await地獄的誕生。
在這篇文章裏我將會解釋什麼是async/await地獄以及分享一些方法去避開它。前端
當咱們在編寫JavaScript異步代碼的時候,經常會在一個接着一個函數調用前添加await關鍵字,影響了函數的調用。由於在通常狀況下,下一個函數的調用並不依賴於前一個函數的調用,可是正是由於添加了await關鍵字,咱們仍須要等待前一個函數調用完畢才能調用下一個函數。數組
假設咱們要寫一段代碼用於購買披薩和飲料,這段代碼以下所示:promise
從表面上看這段代碼沒有問題而且可以運行,可是這不是一個好的方法由於沒有考慮到併發問題。接下來讓咱們仔細分析一下這段代碼而後明確其中問題所在。併發
咱們把這段代碼包裹在了一個異步當即執行函數裏,下列事件會依次發生:異步
就像我在前面提到的那樣,全部的代碼都是一行接着一行執行的,這裏不存在併發執行的狀況。讓咱們仔細想一想,爲何咱們在獲取飲料列表以前須要等待披薩列表的返回?咱們應該嘗試同時獲取飲料和披薩的列表。然而,當咱們須要選擇披薩的時候,咱們須要先獲取披薩的列表。飲料也是如此。
所以咱們能夠得出結論:披薩相關的工做和飲料相關的工做可以同時執行,可是披薩相關的每一步工做須要按次序執行。async
下面這段JavaScript代碼會獲取購物車裏面的物品,而後發送確認訂單的請求。函數
在這種狀況下,for循環在執行下一輪循環以前須要等待當前的sendRequest()函數執行完成。然而,實際上咱們不須要等待,咱們但願儘量快的發送全部請求而後等待他們都完成執行。
如今,我但願你們可以清晰的理解什麼是async/await地獄以及它們對程序的性能影響的嚴重性。如今,我要問一個問題。oop
若是你忘記在異步函數調用的前面添加await關鍵字,這時函數開始執行了,這意味着await並非函數執行的必要條件。這個異步函數會返回一個promise,這個promise咱們能夠在以後使用。性能
另外一個後果就是編譯程序不知道你須要等待這個函數執行完成,所以編譯程序會在這個異步任務尚未完成的時候退出這個程序,因此咱們須要await關鍵字。
promise有一個有趣的性質是:你能夠在前面的代碼獲得這個promise, 而後在後面的代碼中等待這個promise的完成。這是從async/await地獄中解脫的關鍵。spa
正如你所見到的那樣,doSomeAsyncTask()
返回了一個promise。這個時候,doSomeAsyncTask()已經開始執行了。爲了獲得這個promise的結果值,咱們能夠在這個promise前面添加await,JavaScript將會馬上停在這裏再也不執行下一行代碼,直到得到了這個promise的返回值,再執行下一行代碼。
依照如下步驟來逃離async/await地獄。
在咱們第一個例子裏面,咱們在選擇披薩和飲料。於是咱們得出結論:在選擇披薩以前,咱們須要得到披薩的列表;在把披薩加入到購物車以前,咱們須要選擇披薩。咱們能夠認定這三個步驟是互相依賴的,咱們沒法在前一個步驟完成以前執行下一個任務。可是,若是咱們退一步來看,就會發現選擇披薩並不依賴於選擇飲料,咱們能夠同時對他們進行選擇。在這一件事情上,機器能比人類作得更好。
因此咱們已經發現了一些語句依賴於其餘的語句執行而另一些語句並不依賴於其餘。
正如咱們所看到的,選擇披薩須要如下幾個互相依賴的語句:得到披薩列表, 選擇其中一個披薩以及添加到購物車中。咱們應該把這些語句整合在一個異步函數裏面。這樣咱們將會獲得兩個異步函數,selectPizza()和
selectDrink()。
咱們將利用event loop的優點來併發的執行這些非阻塞異步函數。經常使用的一個方法是先返回promise而後使用Promise.all方法。
根據前面提到的三個步驟,咱們把他們運用的咱們的例子中。
如今,咱們已經把這些代碼整合到兩個函數中。在每個函數裏面,每一條代碼的執行依賴於前一條代碼的執行。而後咱們併發的執行selectPizza()
和selectDrink()
。
對於例2,咱們須要解決未知數量的promise,解決這種狀況很是簡單:咱們只須要建立一個數組而後把promise存入其中,而後使用Promise.all()
方法,就可以併發的等待全部的promise返回結果。
但願本文能幫助您看透async/await,並幫助您改進應用程序的性能。