async/await
把咱們從回調地獄中解放了出來,可是,人們也對其很有微詞.由於隨之而來致使了async/await
地獄的誕生.promise
在這篇文章,我會試圖解釋什麼是async/await
地獄,另外我也會分享一些避開它們的方法.併發
當咱們在編寫JavaScript異步代碼的時候,人們常常在一個接着一個的函數調用前面添加await
關鍵字.這會致使性能問題,由於在一般狀況下,一個語句的執行並不依賴前一個語句的執行,可是由於添加了await
關鍵字,你仍舊須要等待前一個語句執行完才能執行一個語句.異步
假設你寫一段代碼用來購買披薩和飲料,這段代碼以下所示.async
(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
})()
複製代碼
從表面上看,這段代碼語法是正確的,而且可以運行.可是,這並非一個好的實現,由於它剔除了併發執行.接下來讓咱們瞭解一下這段代碼是作什麼的,這樣咱們更加明確其中的問題所在.函數
咱們把這段代碼包裹在了一個異步的當即執行函數裏面.下面的事情會按次序發生:oop
就像我在前面提到的那樣,全部的語句都是一行接着一行執行的,這裏不存在併發執行的狀況.讓咱們仔細想一想,爲何咱們在獲取飲料列表以前須要等待披薩列表的返回?咱們應該嘗試同時獲取飲料和披薩的列表.然而,當咱們須要選擇披薩的時候,咱們須要先獲取披薩的列表.飲料也是如此.性能
所以咱們肯定,披薩相關的工做和飲料相關的工做可以同時執行,可是披薩相關的每一步工做須要按次序執行.(順序執行)ui
這段JavaScript代碼會得到購物車裏面的物品,而後發送確認訂單的請求.spa
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
並非函數執行的必要條件.這個異步函數會返回一個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
地獄.
在咱們第一個例子裏面,咱們在選擇披薩和飲料.於是咱們得出結論,在選擇披薩以前,咱們須要得到披薩的列表.同時,在把披薩加入到購物車以前,咱們須要選擇披薩.能夠認爲這三個步驟是互相依賴的,咱們不能在前一個步驟完成以前執行下一個任務. 可是,若是咱們拓寬一下眼界,就會發現選擇披薩並不會依賴於選擇飲料,咱們能夠同時選擇他們.這就是機器能作的比咱們更好的地方. 至此,咱們已經發現了一些語句依賴於其餘的語句執行,可是另一些語句不依賴.
正如咱們所看到的,選擇披薩須要幾個互相依賴的語句,如得到披薩列表,選擇其中一個披薩而後添加到購物車中.咱們應該把這些語句整合在一個異步函數裏面.這樣咱們將會獲得兩個異步函數,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
基礎使用者的行列,同時可以幫助你提升你的程序性能. 若是你喜歡這篇文章,但願可以點贊並收藏.