# 錯誤處理

在 Javascript 當中,難免會出現錯誤。最一般的解決錯誤方式就是使用 try...catch

try {
	JSON.parse('{adfhjkl}');
} catch (err) {
  console.log('you fuck up.');
}

你也可以在 JavaScript 拋出 Error。

function makeError() {
  throw new Error('error!');
}

在 Promise 當中也可以透過 .catch 來捕捉錯誤。

# throw 只能 throw error 嗎?

不只。你要拋物件也可以、字串也可以,undefined, null 都可以。但不太建議這麼做。其中之一是你如果在 console 看看,看到這個錯誤你會做何感想?

明白什麼時候拋出錯誤,以及撰寫清晰的錯誤訊息是必要的。舉例來說:console.warn 會以紅色字標註訊息,並且印出 call stack,但並不會中斷程式執行,throw 如果沒有被 try...catch 包起來,則會中斷程式執行。

除了一般的 Error 之外,Javascript 內建了幾個 ErrorTypeError, ArgumentError

如果要繼承 Error 的話,可以這樣寫:

# 繼承 Error 有什麼好處?

透過繼承 Error 的方式,我們可以透過 Error 的名字來判斷這個錯誤應該要怎麼處理,例如:

class NotFoundError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NotFoundError';
    this.message = message;
  }
}

const catchError = (fn) => {
  try {
	  fn()    
  } catch (err) {
    switch (err.name) {
      case 'NotFoundError':
      case 'Error':
      case 'TypeError':
    }
  }
}

透過 err 這個介面,我們可以統一來處理錯誤,或是再做得更細膩一些:

class APIError extends Error {
  constructor(message) {
    super(message);
    this.name = 'APIError';
    this.message = message;
  }
}

class NotFoundError extends APIError {
   constructor(message) {
    super(message);
    this.name = 'NotFoundError';
    this.message = message;
  } 
}

const catchAPIError = (fn) => {
  try {
    fn()
  } catch (err) {
    if (err instanceof APIError) {
      // do error handling
    }
  }
}

另外,使用 Error 會在程式中幫你記錄相關的 call stack,你可以透過這個資訊來做 debug,另外使用 console.error 也有類似的效果。

##Error Reporting

如果是像一般傳空字串等等的小錯誤,直接用 console.log 之類的方式來提示開發者修正就可以了,像是在 React 當中時常會有貼心的提示,像是忘記加 key、回傳 undefined 等等。

不過如果像是為預期的 error 發生時,我們會希望這些資訊可以送到統計後台當中,或是跳出警告訊息等等,來避免未預期的錯誤持續地在使用者的裝置上發生。

目前像是 Sentry、Rollbar 或是 TrackJS 等等都是蠻不錯的平台,其中我蠻喜歡 Sentry 的介面的,你可以在 catch 的時候送出 error。

另外除了 error 本身,有時送出對應的資訊也會對除錯很有幫助,像是使用者的瀏覽器、作業系統、使用者 id 等等,有這些資訊就可以比較好判斷錯誤是因為瀏覽器支援,還是某些邏輯上寫錯了。

在實作上,可能會有在 development 中並不想把錯誤也送到後台,只想把它印出來幫助除錯,或是在 production 時不要在 console 顯示各種除錯資訊,這時候就蠻適合將這些 log 的行為包裝成 Logger,透過統一的介面來決定 log 訊息。在 nodejs 當中有個蠻著名的套件 winston,支援許多 log 模式,也可以客製化 format 或是 transports 等等,未來也有可能支援瀏覽器端!


現在在 React 或是 Vue 當中,都有針對錯誤處理做出一套解決方案,來針對不同層級的錯誤。

# RxJS 當中的錯誤處理

會特別提到 RxJS 的錯誤處理是我覺得 RxJS 的錯誤處理相當優雅,你可以統一用 Observable 的介面跟操作符來做到這件事:

const stream = Observable.concat(
  Observable.of('hello'),
  Observable.throwError(new Error('oops!'))
).catch(err => {}) // handle error 

你還可以透過像是 retry 的方式讓 error 飛一下,如果 retry 不成功再送到 catch,非常適合場景比較複雜業務邏輯。

# React 中的 ErrorBoundary

在 React 16 後,推出了 ErrorBoundary 的概念,你可以在元件當中使用 componentDidCatch 這個生命週期函數來包裝 React 元件內部拋出的錯誤,讓部分的功能還是能夠正常運作。

實作上常常會把這個功能拆成一個叫做 ErrorBoundary 的元件,你可以把整個應用用 ErrorBoundary 包起來:

<ErrorBoundary>
  <App />
</ErrorBoundary>

在 App 裡頭拋出的錯誤會被 ErrorBoundary 給偵測到並且執行 componentDidCatch,就可以在裡頭做 error logging 的操作。

# Vue 當中的 errorCaptured

errorCaptured 是 vue 組件當中的一個方法,在 vue 當中所有的錯誤都會被傳到 Vue.config.errorHandler,另外在 errorCaptured 當中,可以用返回的 boolean 來決定要不要向上傳播。

# 後記

錯誤處理可以說是在程式開發中最難處理的一環,因為錯誤總是無法預測的,像是在駕駛飛機時,有很重要的一環是練習在單引擎失效下如何操作飛機、如何重飛;在地震的時候電車會自動煞車等等,這些看似不常見的情況,卻是影響產品品質甚鉅的指標。

# 鐵人賽完賽後記

鐵人賽終於告一段落了,首先要感謝 huli,每篇文章都可以被 huli 免費審稿一次跟宣傳一次,覺得安心很多。

當初雖然想說找一個潮潮的技術跟標題,像是 react-hooks 或是 ML 的主題,但轉念一想,其實很早之前就想把自己在前端開發上覺得應該要深入的東西好好介紹一下了,剛好趁這次機會讓自己產出。

主題看起來雖然並不是那麼吸引人,但如果目的不是拿獎項而是讓自己有產出的話,這樣也沒有什麼不好。另外這個系列或許還會再進行一陣子,之後可能會統整成 gitbook 方便以後閱讀。

終於完賽了,爽!