點燈坊

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

使用 Promise 處理相依性 API

Sam Xiao's Avatar 2021-05-24

實務上常遇到須先呼叫第一個 API 取得 id 後,才能以該 id 呼叫第二個 API 取得資料,這種相依性的 API 該如何處理呢 ?

Version

Vue 3.0.11

Composition API

api000

按下 Get Category 會先呼叫 getBook() 取得 categoryId,再將 categoryId 傳入 getCategory() 取得 category

<template lang='pug'>
div
  button(@click='onClick') Get Category
div {{ category }}
</template>

<script setup>
import getBook from './api/getBook'
import getCategory from './api/getCategory'

ref: category = ''

let onClick = () => {
  getBook(1)
    .then(x => x.data.id)
    .then(x => getCategory(x))
    .then(x => x.data.category)
    .then(x => category = x)
    .catch(e => console.error(e))
}
</script>

13 行

let onClick = () => {
  getBook(1)
    .then(x => x.data.id)
    .then(x => getCategory(x))
    .then(x => x.data.category)
    .then(x => category = x)
    .catch(e => console.error(e))
}
  • getBook(1):呼叫 getBook() 取得 categoryId
  • then(x => x.data.id)getBook() 回傳為 Promise,在 Promise 內從 data.id 取得 categoryId
  • then(x => getCategory(x)):呼叫 getCategory() 取得 category
  • then(x => x.data.category):在 Promise 內從 data.category 取得 category
  • then(x => category = x):在 Promise 內將 category 寫入 category state
  • catch(e => console.error(e)):顯示 error response

Point-free

api000

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

<template lang='pug'>
div
  button(@click='onClick') Get Category
div {{ category }}
</template>

<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, thunkify, andThen as then, path, otherwise } from 'ramda'
import { error } from 'wink-fp'
import getBook from './api/getBook'
import getCategory from './api/getCategory'

let category = ref('')

let onClick = pipe(
  thunkify(getBook)(1),
  then(path(['data', 'id'])),
  then(getCategory),
  then(path(['data', 'category'])),
  then(write(category)),
  otherwise(error)
)
</script>

17 行

let onClick = pipe(
  thunkify(getBook)(1),
  then(path(['data', 'id'])),
  then(getCategory),
  then(path(['data', 'category'])),
  then(write(category)),
  otherwise(error)
)
  • thunkify(getBook)(1):將 getBook() 轉成 Thunk 再傳入資料
  • then(path(['data', 'id']))getBook() 回傳為 Promise,在 Promise 內從 data.id 取得 categoryId
  • then(getCategory):呼叫 getCategory() 取得 category
  • then(path(['data', 'category'])):在 Promise 內從 data.category 取得 category
  • then(write(category)):在 Promise 內將 category 寫入 category state
  • otherwise(error):顯示 error response

pipe()

api000

結果不變,但使用 pipe() 抽出 function。

<template lang='pug'>
div
  button(@click='onClick') Get Category
div {{ category }}
</template>

<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, thunkify, andThen as then, path, otherwise } from 'ramda'
import { error } from 'wink-fp'
import getBook from './api/getBook'
import getCategory from './api/getCategory'

let category = ref('')

let getData = pipe(
  getBook,
  then(path(['data', 'id'])),
  then(getCategory),
  then(path(['data', 'category'])),
)

let onClick = pipe(
  thunkify(getData)(1),
  then(write(category)),
  otherwise(error)
)
</script>

17 行

let getData = pipe(
  getBook,
  then(path(['data', 'id'])),
  then(getCategory),
  then(path(['data', 'category'])),
)

getBook()getCategory() 抽成 getData()

由於 getBook() 回傳 Promise,因此後續的 function 都在 then() 內。

24 行

let onClick = pipe(
  thunkify(getData)(1),
  then(write(category)),
  otherwise(error)
)

onClick() 內只需呼叫 getData() 即可。

pipeP()

api000

結果不變,但使用 pipeP() 抽出 function。

<template lang='pug'>
div
  button(@click='onClick') Get Category
div {{ category }}
</template>

<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, thunkify, andThen as then, path, otherwise } from 'ramda'
import { pipeP, error } from 'wink-fp'
import getBook from './api/getBook'
import getCategory from './api/getCategory'

let category = ref('')

let getData = pipeP(
  getBook,
  path(['data', 'id']),
  getCategory,
  path(['data', 'category'])
)

let onClick = pipe(
  thunkify(getData)(1),
  then(write(category)),
  otherwise(error)
)
</script>

17 行

let getData = pipeP(
  getBook,
  path(['data', 'id']),
  getCategory,
  path(['data', 'category'])
)

getData() 功能不變,但改用 pipeP() 組合。

pipeP() 可直接組合 synchronous function 與 asynchronous function,儘管回傳 Promise,也不用將 function 包在 then() 內,因此寫起來較 pipe() 精簡。

Conclusion

  • pipeP()P 即表示 Promise,可直接在 Promise 之後組合 synchronous function 與 asynchronous function 而不需在 then()