聊聊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五、团建、五险一金...

地点上海浦软大厦

相关文章
相关标签/搜索