[譯]async-await 陣列迴圈的幾個坑
[譯]async-await 陣列迴圈的幾個坑
- 原文地址: https://medium.com/dailyjs/th...
- 原文作者:Tory Walker
在 Javascript 迴圈中使用 async/ await
迴圈遍歷陣列似乎很簡單,但是在將兩者結合使用時需要注意一些非直觀的行為。讓我們看看三個不同的例子,看看你應該注意什麼,以及哪個迴圈最適合特定用例。
forEach 迴圈的情況
const urls = [ 'https://jsonplaceholder.typicode.com/todos/1', 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/3' ]; async function getTodos() { await urls.forEach(async (url, idx) => { const todo = await fetch(url); console.log(`Received Todo ${idx+1}:`, todo); }); console.log('Finished!'); } getTodos();
Finished! Received Todo 2, Response: { ··· } Received Todo 1, Response: { ··· } Received Todo 3, Response: { ··· }
:warning:問題 1:
如上述程式碼能夠正常執行。但是注意 Finished!
訊息,被率先列印了。儘管我們使用了 await 但他仍然不會等待所有 await 執行完畢
:warning: 問題 2:
然而,儘管 await 在迴圈中使用,但它並沒有等待每個請求在執行下一個請求之前完成。請求不會按照順序一步一步被髮送出去。如果第一個請求的時間比以下請求的時間長,它仍然可以在最後完成。
因此,根據上述原因,forEach 在和 async/await 搭配使用的時候並不是一個靠得住的東西
Promise.all 方法
我們首先需要解決的就是等待所有迴圈執行完畢。await 操作符返回一個 promise,我們可以使用 Promise.all 方法去並行執行所有的請求。最後去 await 所有 promise 返回的結果
const urls = [ 'https://jsonplaceholder.typicode.com/todos/1', 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/3' ]; async function getTodos() { const promises = urls.map(async (url, idx) => console.log(`Received Todo ${idx+1}:`, await fetch(url)) ); await Promise.all(promises); console.log('Finished!'); } getTodos();
Received Todo 1, Response: { ··· } Received Todo 2, Response: { ··· } Received Todo 3, Response: { ··· } Finished!
我們解決了不等待所有請求執行完畢後列印 Finished!,看起來我們似乎也解決了請求順序的問題。
實際上,上文中已經提到過, Promise.all
方法會按照並行的模式,將所有請求一次性全部發送出去,然後等待接收到全部結果後,按照順序打印出來而已。它並不會按照順序傳送一個請求,收到結果後再發送下一個請求。
這非常適合不需要按照順序傳送的情況,但如果你想要的是序列傳送請求那麼 Promise.all
並不適合
for-of 迴圈
以上的兩種方法並不能完美解決那兩個問題。
for-of
迴圈則能夠按照預期順序執行——等待上一個 await 執行完畢後,再接著下一個。
const urls = [ 'https://jsonplaceholder.typicode.com/todos/1', 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/3' ]; async function getTodos() { for (const [idx, url] of urls.entries()) { const todo = await fetch(url); console.log(`Received Todo ${idx+1}:`, todo); } console.log('Finished!'); } getTodos();
Received Todo 1, Response: { ··· } Received Todo 2, Response: { ··· } Received Todo 3, Response: { ··· } Finished!
我特別喜歡這種使程式碼保持線性的方法,這是使用 async/await 的關鍵優勢之一。我覺得它比其他選擇更容易閱讀。
如果您不需要訪問索引,則程式碼變得更加簡潔:
for(ur url of urls){···}
使用 for...of
迴圈的一個主要缺點是它與Javascript中的其他迴圈選項相比效能不夠好。但是,將效能引數用於await非同步呼叫時,效能引數可以忽略不計,因為目的是在每個呼叫解析之前保持迴圈。我通常只使用 for...of
進行非同步。
當然你也可以使用 for 迴圈得到 for-of 迴圈所有好處。但我還是喜歡 for-of 迴圈帶來的簡潔和高可讀性。