Promise 傳統都用來處理 Asynchronous 代表 未來值
,但由於其具有 Rejected 與 Fulfilled,很類似 Either 的 Left 與 Right,且 then()
又兼具 map()
與 chain()
特性,因此可藉由 Promise 實現 Either 處理 Exception。
Version
Ramda 0.27.1
try … catch
import { pipe, add } from 'ramda'
let div = x => y => {
if (y === 0) throw Error('Can not divide by zero')
else return x / y
}
let f = x => pipe(
div(x),
add(1)
)
f(2)(1) // ?
f(2)(0) // ?
最典型的 Exception 就是 除以 0
,傳統會使用 throw Error
發出 Exception。
第 10 行
let f = x => pipe(
div(x),
add(1)
)
throw
最大問題是與 Function Pipeline 格格不入,我們無法在 Function Pipeline 內使用 try catch
處理 Exception。
Promise as Either
import { pipe, andThen as then, otherwise, add } from 'ramda'
import { resolve, reject } from 'wink-fp'
let div = x => y =>
y === 0 ?
reject('Can not divide by zero') :
resolve(x / y)
let f = x => pipe(
div(x),
then(add(1))
)
f(2)(1) // ?
f(2)(0) // ?
第 4 行
let div = x => y =>
y === 0 ?
reject('Can not divide by zero') :
resolve(x / y)
原本當 y === 0
會發出 Exception,改回傳 Rejected Promise,正常結果則回傳 Fulfilled Promise。
注意在此
div()
並非處理 asynchronous,只是借用 Promise 具有 Rejected 與 Fulfilled 特性,將 Promise 當成 Either Monad 使用
第 9 行
let f = x => pipe(
div(x),
then(add(1))
)
由於 div()
回傳 Promise,因此後續處理的 add(1)
必須包在 then()
內。
此處
then()
相當於 Either Monad 的map()
可發現當 y
傳入 0
時,將回傳 Can not divide by zero
Rejected Promise,類似 Either 的 Left
。
Promise Chain
import { pipe, andThen as then, otherwise, add } from 'ramda'
import { resolve, reject } from 'wink-fp'
let div = x => y =>
y === 0 ?
reject('Can not divide by zero') :
resolve(x / y)
let mul = x => y =>
y == 3 ?
reject('Can not multiply by 3') :
resolve(x * y)
let f = x => pipe(
div(x),
then(add(1)),
then(mul(2))
)
f(2)(0) // ?
f(2)(1) // ?
f(2)(2) // ?
第 9 行
let mul = x => y =>
y == 3 ?
reject('Can not multiply by 3') :
resolve(x * y)
假設 mul()
也是自行實作,且當 y
為 3
時會發出 Exception,其他值則正常處理,也可繼續使用 Rejected Promise 實作 Either 的 Left。
14 行
let f = x => pipe(
div(x),
then(add(1)),
then(mul(2))
)
mul()
雖然回傳 Either 可能造成 Nested Promise,但只要繼續使用 then()
即可,並不需要如 Either 改用 chain()
,因為 Promise 的 then()
會攤平 Nested Promise,相當於 chain()
與 flatMap()
。
因為 Promise 的
then()
兼具map()
與chain()
特性,因此不論使用普通 function,或者回傳 Either 的 function,皆統一使用then()
即可
Composition Law
import { pipe, andThen as then, otherwise, add } from 'ramda'
import { resolve, reject } from 'wink-fp'
let div = x => y =>
y === 0 ?
reject('Can not divide by zero') :
resolve(x / y)
let mul = x => y =>
y == 3 ?
reject('Can not multiply by 3') :
resolve(x * y)
let transform = pipe(
add(1),
mul(2)
)
let f = x => pipe(
div(x),
then(transform)
)
f(2)(0) // ?
f(2)(1) // ?
f(2)(2) // ?
14 行
let transform = pipe(
add(1),
mul(2)
)
let f = x => pipe(
div(x),
then(transform)
)
Promise 必須使用 then()
才能改變其內部值,因此可能發現一堆 then()
。
Promise 支援 composition law,可將 pure function 先使用 pipe()
組合起來,一次傳給 then()
即可。
then() / otherwise()
import { pipe, andThen as then, otherwise, add } from 'ramda'
import { resolve, reject, log, error } from 'wink-fp'
let div = x => y =>
y === 0 ?
reject('Can not divide by zero') :
resolve(x / y)
let f = x => pipe(
div(x),
then(add(1)),
then(log),
otherwise(error)
)
f(2)(1) // ?
f(2)(0) // ?
Promise 畢竟無法用於顯示,因此必須將結果從 Promise 內取出。
第 9 行
let f = x => pipe(
div(x),
then(add(1)),
then(log),
otherwise(error)
)
Fulfilled Promise 可繼續使用 then()
取出,而 Rejected Promise 則由 otherwise()
取出。
Conclusion
- Promise 的 Rejected 與 Fulfilled 類似 Either Monad 的 Left 與 Right,且
then()
又兼具map()
與chain()
特性,因此可將 Promise 視為 Either Monad 使用,並不一定都用在處理 asynchronous