今天來介紹一些跟「錯誤處理」有關的 operators。在使用 RxJS 時,資料流是透過 pipe
及各式各樣的 operators
在處理,且很多時候是非同步的,因此大多時候發生錯誤並不能單純的使用 try...catch
方式處理,就需要透過這些錯誤處理相關的 operators 來幫忙囉!
catchError
catchError
可以在來源 Observable 發生錯誤時,進行額外的處理,一般來說發生錯誤時,都是在訂閱時使用處理:
1 | interval(1000) |
彈珠圖:
1 | ---0---# |
但訂閱畢竟不是整個 Observable 資料流的一部份,而是我們訂閱時自己撰寫的邏輯,如果要將錯誤處理也視為整個 Observable 的一部份,就可以使用 catchError
,catchError
內會傳入錯誤訊息,且需要回傳另一個 Observable,當過程中錯誤發生時,就會改成使用 catchError
回傳的 Observable,讓後續的其他 operators 可以繼續下去,而不會中斷整個資料流:
1 |
|
彈珠圖:
1 | ---0---# |
如果遇到不能處理的問題,也可以就讓錯誤發生,此時只需要回傳 throwError
即可:
1 | interval(1000) |
在 Observable 中,不論是 throw new Error()
還是回傳 throwError()
都會產生錯誤並中斷資料流,所以前面程式使用 map
處理錯誤的邏輯也可以改成:
1 | switchMap(data => iif(() => data % 2 === 0, of(data), throwError('發生錯誤'))) |
會更有 functional programming 的風格!
程式碼:https://stackblitz.com/edit/mastering-rxjs-operator-catcherror
retry
當 Observable 發生錯誤時,可以使用 retry
來重試整個 Observable,在 retry
內可以指定重試幾次:
1 | interval(1000) |
彈珠圖:
1 | ---1---# |
若不指定次數,預設為 -1
,代表會持續重試;若不想重試,也可以指定次數為 0
,就會直接讓錯誤發生。
程式碼:https://stackblitz.com/edit/mastering-rxjs-opereator-retry
retryWhen
retryWhen
也可以再發生錯誤時進行重試,但 retryWhen
更有彈性,在 retryWhen
內需要設計一個 notifier
callback function,retryWhen
會將錯誤資訊傳入 notifier
function,同時需要回傳一個 Observable,retryWhen
會訂閱這個 Observable,每當有事件發生時,就進行重試,直到這個回傳的 Observable 結束,才停止重試。
以下程式在錯誤發生時,會每三秒重試一次,共重試三次:
1 | interval(1000) |
彈珠圖:
1 | -1-# |
由於是讓重試的 Observable 完成,因此整個資料流也會當作「完成」,處理訂閱的 complete()
callback。
如果希望重試幾次次後發生錯誤,一樣加入 throwError
即可:
1 | const retryTimesThenThrowError = (every, times) => interval(every).pipe( |
另外一個小技巧,我們也可以讓使用者自己決定何時要重試:
1 | const click$ = fromEvent(document, 'click'); |
以上程式碼能在錯誤發生後,當滑鼠 click 事件發生時,才進行重試。
程式碼:https://stackblitz.com/edit/mastering-rxjs-operator-retrywhen
finalize
finalize
會在整個來源 Observable 結束時,才進入處理,因此永遠會在最後才呼叫到:
1 | interval(1000) |
從結果可以看到,儘管 map
放在 finalize
後面,但還是不斷的處理 map
內的邏輯,直到來源 Observable 結束後,才進入 finalize
處理,同時也可以注意到 finalize
會比 subsribe
的 complete
還慢進入。
嚴格來說 finalize
不算是錯誤處理的 operator,因為 finalize
會在整個 Observable 結束時才進入處理,跟有沒有發生錯誤無關,但經常與錯誤處理搭配一起使用。
1 | interval(1000) |
從結果可以看到,finalize
也會比 subsribe
的 error
還慢被呼叫。透過 finalize
我們可以確保就算過程中發生錯誤導致整個資料流中斷,還會有個地方可以處理些事情。
程式碼:https://stackblitz.com/edit/mastering-rxjs-operator-finalize
本日小結
catchError
:可以用來決定當來源 Observable 發生錯誤時該如何進行,回傳一個 Observable 代表會使用此 Observable 繼續下去,因此回傳throwError
則代表依然發生錯誤。retry
:當來源 Observable 發生錯誤時,重新嘗試指定次數。retryWhen
:當來源 Observable 發生錯誤時,可以照自定的 Observable 來決定重試的時機。finalize
:在 Observable 結束時,無論是error()
還是complete()
,最後都可以進入finalize
進行最終處理。