點燈坊

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

Promise 之 catch() 與 Catch

Sam Xiao's Avatar 2019-09-17

若為 Fulfilled Promise,我們可用 then()await 去獲得 Synchronous 資料;但若為 Rejected Promise,則有 then()catch()try catch 三種處理方式。

Version

macOS Mojave 10.14.6
VS Code 1.38.1
Quokka 1.0.243
ECMAScript 2017

Promise.resolve()

then()

let fetchData = () => Promise.resolve('Hello World');

fetchData().then(x => console.log(x));

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

catch000

await

let fetchData = () => Promise.resolve('Hello World');

let x = await fetchData();
console.log(x);

亦可使用 ES2017 的 await 處理 fulfilled promise。

catch002

Promise.reject()

then()

let fetchData = () => Promise.reject('Something wrong');

fetchData().then(x => console.log(x));

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

catch003

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

let fetchData = () => Promise.reject('Something wrong');

fetchData().then(
  x => console.log(x), 
  e => console.log(e)
);

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

catch004

藍色的 Something wrong 才是 console.log() 所顯示。

catch()

let fetchData = () => Promise.reject('Something wrong');

fetchData()
  .then(x => console.log(x))
  .catch(e => console.log(e));

ES6 另外提供了 catch() 可專門處理 rejected promise。

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

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

catch005

Try Catch

let fetchData = () => Promise.reject('Something wrong');

try {
  let x = await fetchData();
  console.log(x);
}
catch (e) {
  console.log(e);
}

ES2017 的 await 可搭配傳統的 try catch ,如此處理 fulfilled promise 寫在 try block,而處理 rejected promise 寫在 catch block,這種寫法與傳統 imperative 與 synchronous 相近。

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

catch006

Rejected in Fulfilled

then()

let fetchData = () => Promise.resolve('Hello World');

fetchData().then(
  x => {
    console.log(x);
    return Promise.reject('Something wrong');
  }
).then(x => console.log(x));

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

catch007

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

let fetchData = () => Promise.resolve('Hello World');

fetchData().then(
  x => {
    console.log(x);
    return Promise.reject('Something wrong');
  },
  e => console.log(e),
).then(
  x => console.log(x),
  e => console.log(e)
);

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

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

可發現若要完整處理 rejected promise,則每個 then() 都要提供 rejected handler,如此就違反 DRY

catch008

catch()

let fetchData = () => Promise.resolve('Hello World');

fetchData().then(
  x => {
    console.log(x);
    return Promise.reject('Something wrong');
  }
).then(x => console.log(x)
).catch(e => console.log(e));

若改用 catch(),則 rejected handler 只要寫一次即可,就可處理所有 rejected promise,也符合 functional 風格。

catch009

Try Catch

let fetchData = () => Promise.resolve('Hello World');

try {
  let x = await fetchData();
  console.log(x);
  throw('Something wrong')
}
catch(e) {
  console.log(e);
}

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

若要在 try block 產生 rejected promise,直接使用 throw(),這也是 syntatic sugar。

catch010

Conclusion

  • then() 必須每個 promise 都提供 rejected handler 才能完整處理 rejected promise,違反 DRY
  • 若使用 catch() 只需寫一個 rejected handler 即可,也符合 funtional 的 pipeline 習慣
  • try catch 也只需寫一個 rejected handler 即可,且可繼續使用 imperative 思維

Reference

Marius Schulz, Catch Errors in a JavaScript Promise Chain with Promise.prototype.catch()
MDN, Promise.resolve()
MDN, Promise.reject()
MDN, Promise.prototype.then()
MDN, Promise.prototype.catch()
MDN, await