Array.prototype.forEach
只能接受傳入 Synchronous Function,若要傳入 Asynchronous Function,可依需求使用不同方式實現。
Version
ECMAScript 2017
Synchronous forEach
let data = [1, 2, 3]
data.forEach (x => console.log (x))
console.log ('Finished sync')
forEach
用來處理 side effect,可傳入 sync function,如預期先印出 data
,最後印出 Finished sync
。
Asynchronous forEach
let data = [1, 2, 3]
let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))
data.forEach (async x => {
await sleep (10 - x)
console.log (x)
})
console.log ('Finished async')
若對 forEach
傳入 async function,會發現 Finished async
先印出,然後才印出 data
,且會根據實際 delay 時間印出。
Promise.all + map
let data = [1, 2, 3]
let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))
await Promise.all (data.map (async x => {
await sleep (10 - x)
console.log (x)
}))
console.log ('Finished async')
若需求是 async function 先印出 data
,最後才印出 Finished async
,則必須改用 Promise.all
+ map
組合:
map
:印出data
內每個 element 並回傳 Array PromisePromise.all
:確保 Array Promise 內所有 Promise 都執行完,才回傳新 Promise Arrayawait Promise.all
:由於Promise.all
亦回傳 Promise,因次要加上await
確保 async function 都執行完後才印出Finished async
reduce
let data = [1, 2, 3]
let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))
await data.reduce (async (ac, x) => {
await ac
await sleep (10 - x)
console.log (x)
}, 0)
console.log ('Finished async')
若需求是儘管 async function 先印出 data
,也要根據 code 執行順序印出,而非根據實際 delay 時間印出,則必須改用 reduce
實現:
await ac
:無論 async function 實際 delay 多久,都會被await ac
擋住await reduce
:由於傳入 async function 回傳 Promise,經過reduce
累積後亦回傳 Promise,因次要加上await
確保 async function 都執行完後才印出Finished async
for Loop + await
let data = [1, 2, 3]
let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))
for (let x of data) {
await sleep (10 - x)
console.log (x)
}
console.log ('Finished async')
若需求是儘管 async function 先印出 data
,也要根據 code 執行順序印出,而非根據實際 delay 時間印出,除了使用 reduce
實現外,也可簡單使用 for
loop + await
實現:
await sleep (10 - x)
:無論sleep
delay 多久,await
都會等待sleep
執行完,因此不受實際 delay 影響
Conclusion
map
與reduce
原本不是用來處理 side effect,但由於forEach
接受 async function 時有其限制,只好藉由map
與reduce
實現一些forEach
做不到事情- 由於
forEach
本來就不是設計用來使用 async function,若需求就是不考慮實際 delay 影響,又覺得使用reduce
很 tricky,可改用for
loop +await
較直覺,這也是少數for
loop 較適用場合
Reference
Tamas Sallai, How to use async functions with Array.forEach in JavaScript