點燈坊

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

當 Either 遇見 Promise

Sam Xiao's Avatar 2021-04-25

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

Version

Vue 3.0.11
Sanctuary 3.1.0

Promise Either

either000

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

either001

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

either002

當 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, andThen as then, otherwise, path, ifElse, isEmpty, thunkify } from 'ramda'
import { resolve } from 'wink-fp'
import { Left, Right, create, env } from 'sanctuary'
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(then(path(['data', 'price']))),
  either(resolve)(I),
  then(write(price)),
  otherwise(path(['response', 'data', 'error'])),
  then(write(price))
)
</script>

26 行

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

使用 pipe() 組合出 onClick()

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

20 行

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

使用 ifElse() 組合出 chkEmpty()

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

Conclusion

  • Promise Either 乍聽很嚇人,其實只要把握用 map()chain() 處理 Either,用 then() 處理 Promise,然後將所有 function 處理都寫在 map(then()) 內即可

Reference

Sanctuary, Either