點燈坊

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

使用 Promise.all() 處理 Promise Array

Sam Xiao's Avatar 2021-05-15

若 API 遵循 RESTful 精神設計,常會第一個 API 僅回傳 id Array,若要取得其他值就要呼叫第二個 API,這就形成了 Promise Array,這種實務上常見需求該如何解決呢 ?

Version

Vue 3.0.11

Promise.all()

all000

看似簡單同時顯示 titleprice,但事實上來自兩個 API,一個 API 取得所有 title,另一個 API 根據 titleid 取得 price

<template lang='pug'>
ul
  li(v-for='(x, i) in titles') {{ x }} / {{ prices[i] }}
</template>

<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, andThen as then, otherwise, map, prop, tap, pluck } from 'ramda'
import { error } from 'wink-fp'
import getBooks from '/src/api/getBooks'
import getPrice from '/src/api/getPrice'

let titles = ref([])
let prices = ref([])

let writeTitles = pipe(
  pluck('title'),
  write(titles)
)

let writePrices = pipe(
  pluck('id'),
  map(getPrice),
  x => Promise.all(x),
  then(pluck('data')),
  then(pluck('price')),
  then(write(prices))
)

pipe(
  getBooks,
  then(prop('data')),
  tap(then(writeTitles)),
  then(writePrices),
  otherwise(error)
)()
</script>

第 2 行

ul
  li(v-for='(x, i) in titles') {{ x }} / {{ prices[i] }}
  • 實際上是來自於 titlesprices 兩個 Array
  • 因為 v-for 只能同時使用一個 Array,因此另外一個 Array 只能透過 index 存取

31 行

pipe(
  getBooks,
  then(prop('data')),
  tap(then(writeTitles)),
  then(writePrices),
  otherwise(error)
)()

使用 pipe() 組合 IIFE:

  • getBooks:呼叫 API 取得所有 title,回傳 Promise
  • then(prop('data')):在 Promise 內取得 data prop
  • tap(then(writeTitles)):將資料先寫入 titles state
  • then(writePrices):將資料寫入 prices state
  • otherwise(error):處理 Rejected Promise

17 行

let writeTitles = pipe(
  pluck('title'),
  write(titles)
)

使用 pipe() 組合 writeTitles()

  • pluck('title'):從 Object Array 取得 title prop
  • write(titles):寫入 titles prop

22 行

let writePrices = pipe(
  pluck('id'),
  map(getPrice),
  x => Promise.all(x),
  then(pluck('data')),
  then(pluck('price')),
  then(write(prices))
)

使用 pipe() 組合 writePrices()

  • pluck('id'):從 Object Array 取得 id prop,回傳為 Array
  • map(getPrice):在 Array 中呼叫 API 取得 price,最後為 Promise Array
  • x => Promise.all(x):將 Promise Array 中所有 Promise 都 fulfill
  • then(pluck('data')):從 Object Array 取得 data prop
  • then(pluck('price')):從 Object Array 取得 price prop
  • then(write(prices)):寫入 prices state

Proint-free

all000

結果不變,但使用 Point-free 改寫。

<template lang='pug'>
ul
  li(v-for='(x, i) in titles') {{ x }} / {{ prices[i] }}
</template>

<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, andThen as then, otherwise, map, prop, tap, pluck } from 'ramda'
import { all, error } from 'wink-fp'
import getBooks from '/src/api/getBooks'
import getPrice from '/src/api/getPrice'

let titles = ref([])
let prices = ref([])

let writeTitles = pipe(
  pluck('title'),
  write(titles)
)

let writePrices = pipe(
  pluck('id'),
  map(getPrice),
  all,
  then(pluck('data')),
  then(pluck('price')),
  then(write(prices))
)

pipe(
  getBooks,
  then(prop('data')),
  tap(then(writeTitles)),
  then(writePrices),
  otherwise(error)
)()
</script>

22 行

let writePrices = pipe(
  pluck('id'),
  map(getPrice),
  all,
  then(pluck('data')),
  then(pluck('price')),
  then(write(prices))
)
  • all():使用 Wink-fp 的 all() 取代 Promise.all() 使其 Point-free

seq()

all000

結果不變,但使用 seq() 改寫。

<template lang='pug'>
ul
  li(v-for='(x, i) in titles') {{ x }} / {{ prices[i] }}
</template>

<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, andThen as then, otherwise, map, prop, pluck } from 'ramda'
import { all, seq, error } from 'wink-fp'
import getBooks from '/src/api/getBooks'
import getPrice from '/src/api/getPrice'

let titles = ref([])
let prices = ref([])

let writeTitles = pipe(
  pluck('title'),
  write(titles)
)

let writePrices = pipe(
  pluck('id'),
  map(getPrice),
  all,
  then(pluck('data')),
  then(pluck('price')),
  then(write(prices))
)

pipe(
  getBooks,
  then(prop('data')),
  then(seq(writeTitles, writePrices)),
  otherwise(error)
)()
</script>

31 行

pipe(
  getBooks,
  then(prop('data')),
  then(seq(writeTitles, writePrices)),
  otherwise(error)
)()
  • then(seq(writeTitles, writePrices))writeTitles()writePrices() 兩個 side effect 其實各不相關,可平行執行,可改用 Wink-fp 的 seq() 可讀性更高

Conclusion

  • 在 Array 內呼叫 API 會造成 Promise Array,此時必須使用 Promise.all() 使每個 Promise 都 fulfilled
  • 當必須呼叫多次 API 時,若強求單一 Array 會很難寫,可一個 API 一個 Array,由 template 分擔複雜度
  • 由於各 side effect 都是平行執行,可改用 seq() 改寫