若 API 遵循 RESTful 精神設計,常會第一個 API 僅回傳 id
Array,若要取得其他值就要呼叫第二個 API,這就形成了 Promise Array,這種實務上常見需求該如何解決呢 ?
Version
Vue 3.0.11
Promise.all()
看似簡單同時顯示 title
與 price
,但事實上來自兩個 API,一個 API 取得所有 title
,另一個 API 根據 title
的 id
取得 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] }}
- 實際上是來自於
titles
與prices
兩個 Array - 因為
v-for
只能同時使用一個 Array,因此另外一個 Array 只能透過 index 存取
31 行
pipe(
getBooks,
then(prop('data')),
tap(then(writeTitles)),
then(writePrices),
otherwise(error)
)()
使用 pipe()
組合 IIFE:
getBooks
:呼叫 API 取得所有title
,回傳 Promisethen(prop('data'))
:在 Promise 內取得data
proptap(then(writeTitles))
:將資料先寫入titles
statethen(writePrices)
:將資料寫入prices
stateotherwise(error)
:處理 Rejected Promise
17 行
let writeTitles = pipe(
pluck('title'),
write(titles)
)
使用 pipe()
組合 writeTitles()
:
pluck('title')
:從 Object Array 取得title
propwrite(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,回傳為 Arraymap(getPrice)
:在 Array 中呼叫 API 取得price
,最後為 Promise Arrayx => Promise.all(x)
:將 Promise Array 中所有 Promise 都 fulfillthen(pluck('data'))
:從 Object Array 取得data
propthen(pluck('price'))
:從 Object Array 取得price
propthen(write(prices))
:寫入prices
state
Proint-free
結果不變,但使用 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()
結果不變,但使用 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()
改寫