最近工做中有一個需求是:若是這個請求超時,則進行重試,且重試次數可配置。javascript
首先咱們發請求使用的庫爲:Axios
,其處理請求的位置,是在 redux-observable
中的 epic
裏。java
那麼若是要完成重試機制的話,有兩種辦法:ios
Axios
封裝的函數裏添加劇試代碼epic
裏,使用 RxJS
操做符進行重試。關於 Axios
重試的,其實比較麻煩的,並且須要在原有封裝好的函數裏,繼續添加劇試代碼,總感受不太好。且維護起來也不太方便。因而那就使用 RxJS
操做符進行重試吧。本文代碼將不會套用項目代碼,而是從新寫一個 Demo
,方便理解。git
在 RxJS
中,提供了兩個操做符 retry
和 retryWhen
。github
須要注意的是:重試時,這兩個操做符都會重試整個序列。typescript
且 retry
和 retryWhen
只捕獲 Error
,可是對 Promise
有點無能爲,解決方案文中會說明。redux
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
函數。
運行結果如圖:
首先發出0和1,沒有問題,當val爲2時,拋出錯誤。被 retry
捕獲到,從新走一遍整個 RxJS
序列。因而會發現又發了一次0和1,這個時候又到2了,因而繼續報錯,可是 retry
的重試次數已經用完,則 retry
就不會再管了,直接跳過。因而被 subscribe
中的 error
函數捕獲到。打印出 error
。
上面的 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)
});
複製代碼
運行結果如圖:
其發送邏輯和上面差很少,只是處理的時候不一樣了。
咱們使用 retryWhen
操做符來控制重試的邏輯,咱們先使用 do
操做符,在控制檯打印字符串,再使用 delay
來延遲2秒進行重試。
可是這裏會一直重試,沒有設置重試次數的地方,解決方案在下一章節。
這個時候,咱們發現 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)
});
複製代碼
結果如圖:
發送邏輯沒有變化,可是出現了新的操做符: 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)
});
複製代碼
運行結果如圖:
能夠看到使用了一個新的操做符 takeWhile
。這個操做符接受一個函數,若是這個函數返回了 true
,則繼續把值交給下面的操做符,一旦函數返回 false
,則會觸發 subscribe
中的 complete
,也就是說這個序列已經完成。這樣看的話,你就明白上面的代碼的意圖了。
上文也說了 retry
和 retryWhen
是不支持 Promise.reject()
的,其實這裏的表達不太準確,應該說是 Promise沒有重試的API,當重試的時候Promise
已經在運行中了,因此沒法再次調用該方法。也就形成了 retry
和 retryWhen
不能對 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五、團建、五險一金...
地點上海浦軟大廈