聊聊RxJS中的錯誤重試

前言

最近工做中有一個需求是:若是這個請求超時,則進行重試,且重試次數可配置。javascript

首先咱們發請求使用的庫爲:Axios,其處理請求的位置,是在 redux-observable 中的 epic 裏。java

那麼若是要完成重試機制的話,有兩種辦法:ios

  • 在對 Axios 封裝的函數裏添加劇試代碼
  • epic 裏,使用 RxJS 操做符進行重試。

關於 Axios 重試的,其實比較麻煩的,並且須要在原有封裝好的函數裏,繼續添加劇試代碼,總感受不太好。且維護起來也不太方便。因而那就使用 RxJS 操做符進行重試吧。本文代碼將不會套用項目代碼,而是從新寫一個 Demo,方便理解。git

RxJS 錯誤重試操做符

RxJS 中,提供了兩個操做符 retryretryWhengithub

須要注意的是:重試時,這兩個操做符都會重試整個序列typescript

retryretryWhen 只捕獲 Error,可是對 Promise 有點無能爲,解決方案文中會說明。redux

retry

retry 操做符是用來指定重試次數,好比遇到錯誤了,將會重試n次。如下是 Demo:axios

const source = Rx.Observable.interval(1000)

const example = source.map(val => {
  if (val === 2) {
    throw Error('error');
  }
  return val;
}).retry(1)

example.subscribe({
  next: val => console.log(val),
  error: val => console.log(val.message)
});
複製代碼

在線運行函數

上面的代碼,會每隔1秒鐘發出一次數字序列,當使用 subscribe 訂閱後,一秒鐘後會發出0,第二秒發出1,以此類推。ui

而後每次的數字序列都會到到達 map 操做符裏,在 map 操做符中,咱們能夠看到當數字序列等於2時,則會拋出錯誤。不等於2時 ,則原封不動的返回,最終到達 subscribe 中的 next 函數。

運行結果如圖:

Imgur

首先發出0和1,沒有問題,當val爲2時,拋出錯誤。被 retry 捕獲到,從新走一遍整個 RxJS 序列。因而會發現又發了一次0和1,這個時候又到2了,因而繼續報錯,可是 retry 的重試次數已經用完,則 retry 就不會再管了,直接跳過。因而被 subscribe 中的 error 函數捕獲到。打印出 error

retryWhen

上面的 retry 操做符,只能用來設置重試次數,咱們有時想作成:重試時,打印日誌,或者其餘操做。那麼這個時候 retry 就不太適合了。因此咱們須要 retryWhen 來操做。

代碼以下:

const source = Rx.Observable.interval(1000)

const example = source.map(val => {
  if (val === 2) {
    throw Error('error')
  }
  return val;
}).retryWhen(err => {
  return err
    .do(() => console.log('正在重試'))
    .delay(2000)
})

example.subscribe({
  next: val => console.log(val),
  error: val => console.log(val.message)
});
複製代碼

在線運行

運行結果如圖:

Imgur

其發送邏輯和上面差很少,只是處理的時候不一樣了。

咱們使用 retryWhen 操做符來控制重試的邏輯,咱們先使用 do 操做符,在控制檯打印字符串,再使用 delay 來延遲2秒進行重試。

可是這裏會一直重試,沒有設置重試次數的地方,解決方案在下一章節。

retry + retryWhen

這個時候,咱們發現 retry 能夠設置重試次數,retryWhen 能夠設置重試邏輯。

可是咱們想設置重試次數,又想設置重試邏輯,那應該怎麼辦呢?

OK,先讓咱們看看 retryWhen 操做符。這個操做符若是內部觸發了 Error 或者 Completed,那麼就會中止重試,將會把內部觸發的 Error 或者 Completed 交給 subscribe 的訂閱操做符。可能這樣說,比較麻煩,咱們先上 Demo,按照 Demo 來講,會有助於理解:

const source = Rx.Observable.interval(1000)

const example = source.map(val => {
  if (val === 2) {
    throw Error('error')
  }
  return val;
}).retryWhen(err => {
  return err
    .scan((acc, curr) => {
      if (acc > 2) {
        throw curr
      } 
      return acc + 1
    }, 1)
})

example.subscribe({
  next: val => console.log(val),
  error: val => console.log(val.message)
});
複製代碼

在線運行

結果如圖:

Imgur

發送邏輯沒有變化,可是出現了新的操做符: scan,那麼這個操做符是作什麼用的呢?

能夠把 scan 理解爲 javascript 中的 reduce 函數,這個操做符,具備兩個參數,第一個是回調函數,第二個是默認值。就好比上面的代碼,默認值是1,acc第一次是1,第二次重試時,acc就是2,第三次重試時,acc爲3,已經大於2了,那麼 if 表達式則會true,直接使用 throw 拋出 curr,這裏的 curr 其實就是上面的錯誤原文。上文也說道了,若是在 scan 內初觸發了 Error 則會中止重試,交給下面的 subscribe,而後觸發了訂閱的 error 函數,打印出 error

其實知足重試次數後,把錯誤再拋出去,是比較正常的操做,讓後面的操做符,對錯誤進行處理。可是可能有些人的業務需求是須要返回 Completed,那麼能夠參考下面的代碼:

const source = Rx.Observable.interval(200)

const example = source.map(val => {
  if (val === 2) {
    throw Error('error')
  }
  return val;
}).retryWhen(err => {
  return err
    .scan((acc, curr) => {
      return acc + 1
    }, 0)
    .takeWhile(v => v <= 2)
})

example.subscribe({
  complete: () => console.log('Completed'),
  next: val => console.log(val),
  error: val => console.log(val.message)
});
複製代碼

在線運行

運行結果如圖:

Imgur

能夠看到使用了一個新的操做符 takeWhile。這個操做符接受一個函數,若是這個函數返回了 true,則繼續把值交給下面的操做符,一旦函數返回 false,則會觸發 subscribe 中的 complete,也就是說這個序列已經完成。這樣看的話,你就明白上面的代碼的意圖了。

解決Promise問題

上文也說了 retryretryWhen 是不支持 Promise.reject() 的,其實這裏的表達不太準確,應該說是 Promise沒有重試的API,當重試的時候Promise 已經在運行中了,因此沒法再次調用該方法。也就形成了 retryretryWhen 不能對 Promise 進行重試。那麼解決方案也很簡單了。

咱們可使用 defer 操做符,如今來簡單說明下這個操做符的用處。

defer 接受一個函數參數,其函數不會運行,只有你使用 subscribe 去訂閱的時候,纔會去運行函數。而且運行函數,都是在獨立的運行空間內,也就說,即便咱們使用 Promise,也不會形成沒法重試的狀況,由於它不是複用以前的結果,而是從新開啓一個新的內存空間,去運行函數,返回函數結果。

那麼咱們就能夠把代碼寫成下面這樣:

const getInfo: AxiosPromise = axios.get('http://xxx.com')
const exp = defer(() => getInfo)
  .retryWhen(err => {
    return err.scan((acc, curr) => {
      if (acc > 2) {
        throw curr
      }
      
      return acc + 1
    }, 1)
  })

example.subscribe({
  next: val => console.log(val),
  error: val => console.log(val.message)
});
複製代碼

做者信息

Black-Hole: 158blackhole@gmail.com

Blog: www.bugs.cc

Github: github.com/BlackHole1

其餘

我司(愛樂奇)招人,感興趣的小夥伴能夠來投簡歷呀。

彈性工做制、每日水果、同事都特別nice、96五、團建、五險一金...

地點上海浦軟大廈

相關文章
相關標籤/搜索