如何串行或者並行運行異步循環?
在使用循環處理異步的魔法之前,我們先來看下我們是怎么處理同步循環的。
同步循環
很久以前我寫的循環是這樣的:
for (var i = 0; i < array.length; i++) { var item = array[i]; // do something with item }
后來 JavaScript 提供了很多新的特性,現在我們會更傾向於用下面這種寫法:
array.forEach((item) => { // do something with item })
在開發過程可能會有這么一種需求,我們需要在循環中異步處理 item,那么可以怎么做呢?
異步循環
如何在循環中使用 await?我們試着寫一個異步函數,然后 await 每一次循環任務。
async function processArray(array) { array.forEach(() => { // define synchronous anonymous function // it will throw error here await func(item) }); }
這個代碼會拋出一個錯誤,因為我們不能在同步方法中使用 await, processArray 確實是異步函數,但是 array.forEach 里的匿名函數是同步的。
1. 不要等待結果
要處理這個問題,我們可以把這個匿名函數定義為異步的:
async function processArray(array) { array.forEach(() => { await delayedLog(item) }); console.log('Done!'); }
但是這樣的話 forEach 方法就相當於異步的了,不會等待遍歷完所有的 item,例如下面這段代碼:
function delay () { return new Promise(resolve => setTimeout(resolve, 300)); } async function delayedLog(item) { // notice that we can await a function that returns promise await delay(); // log item only after a delay console.log(item); } async function processArray(array) { array.forEach(() => { await delayedLog(item) }); console.log('Done!'); } processArray([1, 2, 3]);
將會輸出:
Done! 1 2 3
如果你不需要等待這個循環完成,這樣就已經可以了。但是大部分情況我們還是需要等待這個循環完成才進行之后的操作。
2. 串行遍歷
要等待所有的結果返回,我們還是要回到老式的 for 循環寫法:
async function processArray(array) { for (const item of arr) { await delayedLog(item); } console.log('Done!'); }
最后的結果符合我們的預期:
1
2
3
Done!
上面這段的遍歷代碼是串行執行的,我們也可以把它換成並行的。
3. 並行遍歷
我們可以稍微更改上面的代碼來編程並行的:
async function processArray(array) { // map() 方法創建一個新數組,其結果是該數組中的每個元素都調用一個提供的函數后返回的結果。 // async 修飾的方法返回值是一個promise對象,因此下面map的返回值就是一個promise列表 const promiseArr = array.map(delayedLog); // wait until all promises are resolved await Promise.all(promiseArr); console.log('Done!'); }
(注意:對於特別大的數組不建議使用這種寫法,太多的並行任務會加重 CPU 和內存的負荷)
轉自:https://zhuanlan.zhihu.com/p/31000936