點燈坊

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

當 Either 遇見 Future

Sam Xiao's Avatar 2021-06-03

當有 Function 回傳 Either,而後續的 Function 都在 Either 內執行時,卻又有 Function 回傳 Future,這就造成 Either 內有 Future 窘境,實務上該如何處理這種兩層 Monad 呢 ?

Either Future

future000

當沒有輸入任何 ID 時,會顯示 ID is empty,且不會繼續呼叫 API 查詢價錢。

future001

當輸入 ID 時,就會繼續呼叫 API 查詢價錢。

future002

當 API 回傳 error response 時,則顯示 error message。

<template lang='pug'>
input(v-model='id')
button(@click='onClick') Submit
span {{ price }}
</template>

<script setup>
import { ref } from 'vue'
import { read, write } from 'vue3-fp'
import { pipe, map, path, ifElse, isEmpty, thunkify } from 'ramda'
import { Left, Right, create, env } from 'sanctuary'
import { mapRej, fork, resolve } from 'fluture'
import getPrice from '/src/api/getPrice_'

let { either, I } = create({ checkTypes: false, env })

let id = ref('')
let price = ref('')

let chkEmpty = ifElse(
  isEmpty,
  thunkify(Left)('ID is empty'),
  Right
)

let onClick = pipe(
  read(id),
  chkEmpty,
  map(getPrice),
  map(map(path(['data', 'price']))),
  map(mapRej(path(['response', 'data', 'error']))),
  either(resolve)(I),
  fork(write(price))(write(price))
)
</script>

27 行

let onClick = pipe(
  read(id),
  chkEmpty,
  map(getPrice),
  map(map(path(['data', 'price']))),
  map(mapRej(path(['response', 'data', 'error']))),
  either(resolve)(I),
  fork(write(price))(write(price))
)

使用 pipe() 組合出 onClick()

  • read(id):讀取 id state
  • chkEmpty:檢查輸入是否為空白,並回傳 Either
  • map(getPrice):呼叫 getPrice() 從 API 查詢 price 並回傳 Future
  • map(map(path(['data', 'price']))):關鍵在此行,Future 在 Either 內,因此只能 map()先拆第一層 Either,再用 map() 拆第二層 Future,所有的 function 都在 map(map())
  • map(mapRej(path(['response', 'data', 'error']))):處理 Rejected Future,在 Rejected Future 內取得 response.date.error
  • either(resolve)(I):處理 Left Either,將錯誤訊息包成 Future 回傳
  • fork(write(price))(write(price)):從 Rejected Future 與 Resolved Future 內取出資料寫入 price state

21 行

let chkEmpty = ifElse(
  isEmpty,
  thunkify(Left)('ID is empty'),
  Right
)

使用 ifElse() 組合出 chkEmpty()

  • isEmpty:檢查輸入是否為空白
  • thunkify(Left)('ID is empty'):若輸入為空白,則回傳 Left Either,相當於 Exception
  • Right:正常值則回傳 Right Either

Conclusion

  • Future Either 乍聽很嚇人,其實 Either 與 Future 都是 Monad,只要把握使用 map()chain() 一層一層處理 Either 與 Future,最後先使用 either() 處理 Either,再使用 fork() 處理 Future 即可