We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最近工作中有一个需求是:如果这个请求超时,则进行重试,且重试次数可配置。
首先我们发请求使用的库为:Axios,其处理请求的位置,是在 redux-observable 中的 epic 里。
Axios
redux-observable
epic
那么如果要完成重试机制的话,有两种办法:
RxJS
关于 Axios 重试的,其实比较麻烦的,而且需要在原有封装好的函数里,继续添加重试代码,总感觉不太好。且维护起来也不太方便。于是那就使用 RxJS 操作符进行重试吧。本文代码将不会套用项目代码,而是重新写一个 Demo,方便理解。
Demo
在 RxJS 中,提供了两个操作符 retry 和 retryWhen。
retry
retryWhen
需要注意的是:重试时,这两个操作符都会重试整个序列。
且 retry 和 retryWhen 只捕获 Error,但是对 Promise 有点无能为,解决方案文中会说明。
Error
Promise
retry 操作符是用来指定重试次数,比如遇到错误了,将会重试n次。以下是 Demo:
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,以此类推。
subscribe
然后每次的数字序列都会到到达 map 操作符里,在 map 操作符中,我们可以看到当数字序列等于2时,则会抛出错误。不等于2时 ,则原封不动的返回,最终到达 subscribe 中的 next 函数。
map
next
运行结果如图:
首先发出0和1,没有问题,当val为2时,抛出错误。被 retry 捕获到,重新走一遍整个 RxJS 序列。于是会发现又发了一次0和1,这个时候又到2了,于是继续报错,但是 retry 的重试次数已经用完,则 retry 就不会再管了,直接跳过。于是被 subscribe 中的 error 函数捕获到。打印出 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秒进行重试。
do
delay
但是这里会一直重试,没有设置重试次数的地方,解决方案在下一章节。
这个时候,我们发现 retry 可以设置重试次数,retryWhen 可以设置重试逻辑。
但是我们想设置重试次数,又想设置重试逻辑,那应该怎么办呢?
OK,先让我们看看 retryWhen 操作符。这个操作符如果内部触发了 Error 或者 Completed,那么就会停止重试,将会把内部触发的 Error 或者 Completed 交给 subscribe 的订阅操作符。可能这样说,比较麻烦,我们先上 Demo,按照 Demo 来说,会有助于理解:
Completed
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
可以把 scan 理解为 javascript 中的 reduce 函数,这个操作符,具有两个参数,第一个是回调函数,第二个是默认值。就比如上面的代码,默认值是1,acc第一次是1,第二次重试时,acc就是2,第三次重试时,acc为3,已经大于2了,那么 if 表达式则会true,直接使用 throw 抛出 curr,这里的 curr 其实就是上面的错误原文。上文也说道了,如果在 scan 内初触发了 Error 则会停止重试,交给下面的 subscribe,然后触发了订阅的 error 函数,打印出 error。
javascript
reduce
if
throw
curr
其实满足重试次数后,把错误再抛出去,是比较正常的操作,让后面的操作符,对错误进行处理。但是可能有些人的业务需求是需要返回 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,也就是说这个序列已经完成。这样看的话,你就明白上面的代码的意图了。
takeWhile
true
false
complete
上文也说了 retry 和 retryWhen 是不支持 Promise.reject() 的,其实这里的表达不太准确,应该说是 Promise没有重试的API,当重试的时候Promise 已经在运行中了,所以无法再次调用该方法。也就造成了 retry 和 retryWhen 不能对 Promise 进行重试。那么解决方案也很简单了。
Promise.reject()
我们可以使用 defer 操作符,现在来简单说明下这个操作符的用处。
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) });
The text was updated successfully, but these errors were encountered:
No branches or pull requests
聊聊RxJS中的错误重试
前言
最近工作中有一个需求是:如果这个请求超时,则进行重试,且重试次数可配置。
首先我们发请求使用的库为:
Axios
,其处理请求的位置,是在redux-observable
中的epic
里。那么如果要完成重试机制的话,有两种办法:
Axios
封装的函数里添加重试代码epic
里,使用RxJS
操作符进行重试。关于
Axios
重试的,其实比较麻烦的,而且需要在原有封装好的函数里,继续添加重试代码,总感觉不太好。且维护起来也不太方便。于是那就使用RxJS
操作符进行重试吧。本文代码将不会套用项目代码,而是重新写一个Demo
,方便理解。RxJS 错误重试操作符
在
RxJS
中,提供了两个操作符retry
和retryWhen
。需要注意的是:重试时,这两个操作符都会重试整个序列。
且
retry
和retryWhen
只捕获Error
,但是对Promise
有点无能为,解决方案文中会说明。retry
retry
操作符是用来指定重试次数,比如遇到错误了,将会重试n次。以下是Demo
:在线运行
上面的代码,会每隔1秒钟发出一次数字序列,当使用
subscribe
订阅后,一秒钟后会发出0,第二秒发出1,以此类推。然后每次的数字序列都会到到达
map
操作符里,在map
操作符中,我们可以看到当数字序列等于2时,则会抛出错误。不等于2时 ,则原封不动的返回,最终到达subscribe
中的next
函数。运行结果如图:
首先发出0和1,没有问题,当val为2时,抛出错误。被
retry
捕获到,重新走一遍整个RxJS
序列。于是会发现又发了一次0和1,这个时候又到2了,于是继续报错,但是retry
的重试次数已经用完,则retry
就不会再管了,直接跳过。于是被subscribe
中的error
函数捕获到。打印出error
。retryWhen
上面的
retry
操作符,只能用来设置重试次数,我们有时想做成:重试时,打印日志,或者其他操作。那么这个时候retry
就不太适合了。所以我们需要retryWhen
来操作。代码如下:
在线运行
运行结果如图:
其发送逻辑和上面差不多,只是处理的时候不同了。
我们使用
retryWhen
操作符来控制重试的逻辑,我们先使用do
操作符,在控制台打印字符串,再使用delay
来延迟2秒进行重试。但是这里会一直重试,没有设置重试次数的地方,解决方案在下一章节。
retry + retryWhen
这个时候,我们发现
retry
可以设置重试次数,retryWhen
可以设置重试逻辑。但是我们想设置重试次数,又想设置重试逻辑,那应该怎么办呢?
OK,先让我们看看
retryWhen
操作符。这个操作符如果内部触发了Error
或者Completed
,那么就会停止重试,将会把内部触发的Error
或者Completed
交给subscribe
的订阅操作符。可能这样说,比较麻烦,我们先上Demo
,按照Demo
来说,会有助于理解:在线运行
结果如图:
发送逻辑没有变化,但是出现了新的操作符:
scan
,那么这个操作符是做什么用的呢?可以把
scan
理解为javascript
中的reduce
函数,这个操作符,具有两个参数,第一个是回调函数,第二个是默认值。就比如上面的代码,默认值是1,acc第一次是1,第二次重试时,acc就是2,第三次重试时,acc为3,已经大于2了,那么if
表达式则会true,直接使用throw
抛出curr
,这里的curr
其实就是上面的错误原文。上文也说道了,如果在scan
内初触发了Error
则会停止重试,交给下面的subscribe
,然后触发了订阅的error
函数,打印出error
。其实满足重试次数后,把错误再抛出去,是比较正常的操作,让后面的操作符,对错误进行处理。但是可能有些人的业务需求是需要返回
Completed
,那么可以参考下面的代码:在线运行
运行结果如图:
可以看到使用了一个新的操作符
takeWhile
。这个操作符接受一个函数,如果这个函数返回了true
,则继续把值交给下面的操作符,一旦函数返回false
,则会触发subscribe
中的complete
,也就是说这个序列已经完成。这样看的话,你就明白上面的代码的意图了。解决Promise问题
上文也说了
retry
和retryWhen
是不支持Promise.reject()
的,其实这里的表达不太准确,应该说是 Promise没有重试的API,当重试的时候Promise
已经在运行中了,所以无法再次调用该方法。也就造成了retry
和retryWhen
不能对Promise
进行重试。那么解决方案也很简单了。我们可以使用
defer
操作符,现在来简单说明下这个操作符的用处。defer
接受一个函数参数,其函数不会运行,只有你使用subscribe
去订阅的时候,才会去运行函数。并且运行函数,都是在独立的运行空间内,也就说,即使我们使用Promise
,也不会造成无法重试的情况,因为它不是复用之前的结果,而是重新开启一个新的内存空间,去运行函数,返回函数结果。那么我们就可以把代码写成下面这样:
The text was updated successfully, but these errors were encountered: