點燈坊

失くすものさえない今が強くなるチャンスよ

在 reduce 內使用 Asynchronous Function

Sam Xiao's Avatar 2021-08-09

Array.prototype.reduce 只能接受傳入 Synchronous Function,若要傳入 Asynchronous Function,可依需求使用不同方式實現。

Version

ECMAScript 2017

Synchronous reduce

let data = [1, 2, 3]

data.reduce ((ac, x) => {
  return ac + x
}) // ?

reduce 會將 Array 計算成單一 value,可傳入 sync function,如預期回傳加總值。

reduce000

Asynchronous reduce

await Last

let data = [1, 2, 3]

let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))

await data.reduce (async (ac, x) => {
  await sleep (1)
  return (await ac) + x
}) // ?

若傳入 reduce 為 async function,此時 ac 亦成為 Promise,故也須搭配 await 解開 Promise。

reduce001

await First

let data = [1, 2, 3]

let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))

await data.reduce (async (ac, x) => {
  await ac
  await sleep (1)
  return (await ac) + x
}) // ?

雖然都是 async function,但 await 寫法不同,在 timing 上會有些微差異:

  • await first:一開始加上 await ac,則 async function 將如同 sync function 依序執行,不受 sleep 時間影響
  • await lastsleep 仍平行執行,會受 sleep 時間影響

本例因為 Number 相加支援 associative law,因此 sleep 所造成的 timing 差異並不影響結果,若針對不支援 associative law 的 type 運算,就必須考慮 async function 本身的 timing 差異

reduce002

for Loop + await

let data = [1, 2, 3]

let sleep = ms => new Promise (resolve => setTimeout (resolve, ms))

let result = 0

for (let x of data) {
  await sleep (1)
  result = result + x
}

result // ?

若覺得 reduce 處理 async function 很麻煩,也可簡單使用 for loop + await 實現,此時相當於 reduce + await first 寫法,並不受 sleep 時間影響。

reduce003

Conclusion

  • reduce 內使用 async function 時,分 await last 與 await first 寫法,await last 會受 timing 影響,而 await first 則不受 timing 影響
  • reduce 本來就不是設計用來使用 async function,若覺得 reduce 處理 async function 很麻煩,可改用 for loop + await 較直覺,這也是少數 for loop 較適用場合,等效於 await first 寫法

Reference

Tamas Sallai, How to use async functions with Array.reduce in JavaScript