如何使用RxJS重试超时持续时间?

时间:2017-01-26 11:42:02

标签: angular rxjs reactive-programming rxjs5 typescript2.0

我正在处理最新的Angular和Typescript以及RxJS 5.

Angular目前使RxJS成为必需品。我主要使用C#超过10年,而且我非常习惯使用Linq / Lambdas /流利语法,我认为这种语法是Reactive的基础。

我想在重试时使用递增的超时值进行Http get调用,但是我在查看如何执行此操作时仍然遇到问题,并且仍然将所有内容保留在管道中(不使用外部状态)。

我知道我可以这样做,但它只会使用相同的超时值重试。

myHttpObservable.timeout(1000).retry(2);

RxJS的文档在很多地方都很差,在这里询问它只是删除了我的问题,这很难过......所以我被迫查看来源。

有没有办法在每次以一种保持状态在管道中的方式重试超时持续时间增加?另外,我想在第一次尝试时获得一个初始超时。

我一开始尝试过与此相似的东西,但实现了令人困惑的重试当操作符并非真正用于我想要的东西时:

myHttpObservable.timeout(1000).retryWhen((theSubject: Observable<Error>) => {
 return  aNewMyObservableCreatedinHere.timeout(2000);  
});

我知道我可以使用外部状态来实现这一点,但我基本上都在寻找一种优雅的解决方案,我认为这是他们用反应式编程方式所推动的。

5 个答案:

答案 0 :(得分:9)

目前RxJs5最大的问题之一就是文档。它确实是碎片化的,与之前的版本不相上下。通过查看the documentation of RxJs4 you can see that .retryWhen() 已经有example用于构建指数退避,可以轻松迁移到RxJs5:

Rx.Observable.throw(new Error('splut'))
  .retryWhen(attempts => Rx.Observable.range(1, 3)
    .zip(attempts, i => i)
    .mergeMap(i => {
      console.log("delay retry by " + i + " second(s)");
      return Rx.Observable.timer(i * 1000);
    })
  ).subscribe();

答案 1 :(得分:4)

根据我之前回答的评论中的其他输入,我一直在尝试使用.expand()运算符来解决您的要求:

  

我想用超时持续时间= X得到一个get,如果它超时,   然后使用timeout = X + 1

重试

以下代码段以timeout = 500开头,并且对于每次尝试,超时都会随着尝试* 500 而增加,直到达到 maxAttempts 或成功检索到结果:

getWithExpandingTimeout('http://reaches-max-attempts', 3)
  .subscribe(
    res => console.log(res),
    err => console.log('ERROR: ' + err.message)
  );

getWithExpandingTimeout('http://eventually-finishes-within-timeout', 10)
  .subscribe(
    res => console.log(res),
    err => console.log('ERROR: ' + err.message)
  );



/*
retrieve the given url and keep trying with an expanding 
timeout until it succeeds or maxAttempts has been reached
*/
function getWithExpandingTimeout(url, maxAttempts) {
  return get(url, 1)
    .expand(res => {
      if(res.error) {
        const nextAttempt = 1 + res.attempt;
        if(nextAttempt > maxAttempts) {
          // too many retry attempts done
          return Rx.Observable.throw(new Error(`Max attempts reached to retrieve url ${url}: ${res.error.message}`));
        }
        
        // retry next attempt
        return get(url, nextAttempt);
      }
      return Rx.Observable.empty(); // done with retrying, result has been emitted
    })
    .filter(res => !res.error);
}

/*
 retrieve info from server with timeout based on attempt
 NOTE: does not errors the stream so expand() keeps working
*/
function get(url, attempt) {
  return Rx.Observable.of(`result for ${url} returned after #${attempt} attempts`)
    .do(() => console.log(`starting attempt #${attempt} to retrieve ${url}`))
    .delay(5 * 500)
    .timeout(attempt * 500)
    .catch(error => Rx.Observable.of({ attempt: attempt, error }));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.0/Rx.js"></script>

这是如何工作的

上游或.expand()运算符生成的每个值都向下游发出,并用作.expand()运算符的输入。这将继续,直到不再发出任何值。通过利用此行为,当发射内部包含.get()值时,.error函数将尝试重新尝试。

.get()函数不会抛出错误,因为否则我们需要在.expand()中捕获它,否则递归会突破意外。

当超出 maxAttempts 时,.expand()运算符会抛出一个错误停止递归。当发射中没有.error属性时,我们希望这是一个成功的结果,并发出一个空的Observable,停止递归。

注意:它使用.filter()根据.error属性删除所有排放,因为.expand()生成的所有值也会向下游发出,但这些.error值是内部的状态。

答案 2 :(得分:2)

GradientFinder npm程序包中有一个运算符称为retryBackoff。我在backoff-rxjs的文章中对此进行了描述,但简而言之就是:

source.pipe(
  retryWhen(errors => errors.pipe(
      concatMap((error, iteration) => 
          timer(Math.pow(2, iteration) * initialInterval, maxInterval))));

此处是blog.angularindepth.com,用于可自定义的版本。

答案 3 :(得分:2)

我使用delayWhen创建一个通告程序函数,该函数使retryWhen在每次错误发生后都会随着延迟的增加而发出。您可以通过更改用于计算重试之间的延迟的公式来选择不同的时间序列。有关几何和指数补偿重试策略,请参见下面的两个示例公式。另请注意,我如何限制重试次数并在达到该限制时抛出错误。

const initialDelay = 1000;
const maxRetries = 4;
throwError('oops')
  .pipe(
    retryWhen(errors =>
      errors.pipe(
        delayWhen((_,i) => {
          // const delay = (i+1)*initialDelay;            // geometric
          const delay = Math.pow(2,i)*initialDelay;       // exponential
          console.log(`retrying after ${delay} msec...`);
          return timer(delay);
        }),
        take(maxRetries),
        concat(throwError('number of retries exceeded')))))
  .subscribe(
    x => console.log('value:', x),
    e => console.error('error:', e)
  );

答案 4 :(得分:0)

我认为退避不正确。这个延迟是对下一个请求的错误,不是对下一个请求的请求,rxjs中的超时说的是源花费时间大于超时,我想改变请求的超时,而不是添加之间的延迟错误和下一个请求,谁知道怎么做?

像这样:
请求:8s,超时:3s,超时更改:3 * 2 * retryIndex
期望:此请求将重试两次并最终花费 8 秒。
Backoff:不要使用超时,这个请求会在第一时间成功。如果请求有另一个错误,下一个请求将在 3 * 2 * retryIndex 之后发送。