點燈坊

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

使用 otherwise() 處理 Rejected Promise

Sam Xiao's Avatar 2020-11-20

ECMAScript 2015 提供了 catch() 處理 Rejected Promise,而 2017 更提供了 try catch 直接在 catch Block 處理;事實上 Ramda 也提供了 otherwise(),讓我們以 Function Pipeline 處理 Rejected Promise。

Version

Ramda 0.27.1

Fulfilled Promise

then()

let f = () => Promise.resolve(1)

f().then(console.log)

若使用 Promise.resolve() 回傳 Fulfilled Promise,可用 then() 取得內部資料。

catch000

Await

let f = () => Promise.resolve(1)

await f() // ?

亦可使用 ES2017 的 await 處理 Fulfilled Promise。

catch002

Rejected Promise

then()

let f = () => Promise.reject('error')

f().then(console.log)

若使用 Promise.reject() 回傳 Rejected Promise,則 then() 的 callback 將不會被執行。

catch003

紅色的 error 並非 console.log() 顯示,只是 Quokka 貼心地顯示 Rejected Promise 未處理,若在 browser 或 Node 則是 runtime 錯誤。

let f = () => Promise.reject('error')

f().then(console.log, console.error)

若要使用 then(),正確寫法要提供第二個 callback,專門負責處理 Rejected Promise。

catch004

紅色的 error 才是 console.error() 所顯示。

catch()

let f = () => Promise.reject('error')

f()
  .then(console.log)
  .catch(console.error)

ES6 另外提供了 catch() 可專門處理 Rejected Promise。

如此 then() 只要專心處理 Fulfilled Promise 即可,更符合 SRP且可讀性更高。

.then(undefined, callback) 相當於 .catch(callback)

catch005

Try Catch

let f = () => Promise.reject('error')

try {
  let x = await f()
  console.log(x)
}
catch (e) {
  console.error(e)
}

ES2017 的 await 可搭配傳統的 try catch ,如此處理 Fulfilled Promise 寫在 try block,而處理 Rejected Promise 寫在 catch block,與傳統 Imperative 與 synchronous 相近。

try catch 只是 .catch() 的 syntatic sugar 而已

catch006

Ramda

import { pipe, andThen as then, otherwise } from 'ramda'

let f = () => Promise.reject('error')

pipe(
  f,
  then(console.log),
  otherwise(console.error)
)()

Ramda 已經提供 then() 讓我們處理 Fulfilled Promise,同理也提供了 otherwise() 處理 Rejected Promise。

otherwise()
(a → b) → (Promise e a) → (Promise e b)
Promise.prototype.catch() 的 function 版本

(a -> b):synchronous function

Promise e a:data 為 Promise

Promise e b:回傳亦為 Promise

catch011

Rejected in Fulfilled

then()

let f = () => Promise.resolve(1)

f()
  .then(x => (console.log(x), Promise.reject('error')))
  .then(console.log)

比較複雜的是若在 then() 的 callback 中回傳 Rejected Promise 時,若下一個 then() 只提供一個 callback,一樣無法處理剛產生的 rejected promise。

catch007

error 無法被 console.log() 顯示,紅色的 Something worng 是 Quokka 所顯示。

let f = () => Promise.resolve(1)

f()
  .then(x => (console.log(x), Promise.reject('error')), console.error,)
  .then(console.log, console.error)

若要正確顯示 error,則第二個 then() 也要提供第二個 callback。

同理若要擔心第一個 Rejected Promise,第一個 then() 也必須提供第二個 callback。

可發現若要完整處理 Rejected Promise,則每個 then() 都要提供 rejected handler 很麻煩

catch008

catch()

let f = () => Promise.resolve(1)

f()
  .then(x => (console.log(x), Promise.reject('error')))
  .then(console.log)
  .catch(console.error)

若改用 catch(),則 Rejected Handler 只要寫一次即可,就可處理所有 Rejected Promise。

catch009

Try Catch

let f = () => Promise.resolve(1)

try {
  let x = await f()
  console.log(x)
  throw('error')
}
catch(e) {
  console.log(e)
}

若使用 ES2017 的 await,則 Rejected Handler 一樣只要寫一次即可,也符合原本 Imperative 與 synchronous 習慣。

catch010

Ramda

import { pipe, andThen as then, otherwise } from 'ramda'

let f = () => Promise.resolve(1)

pipe(
  f,
  then(x => (console.log(x), Promise.reject('error'))),
  otherwise(console.error)
)()

若在 then() 回傳 Rejected Promise,則 otherwise() 也能攔截得到 error,與 catch()try catch 一樣只需一個 Rejected Handler 即可。

catch013

Conclusion

  • then() 必須每個 Promise 都提供 Rejected Handler 才能完整處理 rejected promise,若使用 catch() 只需寫一個 Rejected Handler 即可
  • try catch 也只需寫一個 Rejected Handler 即可,且可繼續使用 Imperative 思維
  • otherwise() 也只需寫一個 Rejected Handler 即可,由於其 functional 特質,可繼續使用 Function Pipeline

Reference

Marius Schulz, Catch Errors in a JavaScript Promise Chain with Promise.prototype.catch()
Ramda, otherwise()