rxjs custom retryWhen стратегия с автоматическим увеличением задержки не работает должным образом - PullRequest
0 голосов
/ 14 октября 2019

Я пытаюсь создать собственную стратегию retryWhen, которая пытается retry N раз с промежуточной задержкой X и неудачей впоследствии. В некоторой степени пример learnrxjs.io - это именно то, что я ищу.

К сожалению, есть проблема с этим кодом, которую я не могу понять, как решить. В моем случае наблюдаемое может случайно произойти сбой - у вас может быть 2 successful попыток, а затем 2 unsuccessful попыток. Через некоторое время подписка автоматически завершится, потому что retryAttempt s превысит максимум, хотя на практике этого не произошло.

Чтобы лучше понять проблему, я создал StackBlitz

Ответ будет:

  Attempt 1: retrying in 1000ms
  0
  1
  Attempt 2: retrying in 2000ms
  Attempt 3: retrying in 3000ms
  0
  1
  We are done!

Но на самом деле это должно быть

  Attempt 1: retrying in 1000ms
  0
  1
  Attempt 1: retrying in 1000ms <-- notice counter starts from 1
  Attempt 2: retrying in 2000ms
  0
  1
  Attempt 1: retrying in 1000ms <-- notice counter starts from 1
  0
  1
  Attempt 1: retrying in 1000ms <-- notice counter starts from 1
  Attempt 2: retrying in 2000ms
  0
  1
  ... forever

Я чувствую, что здесь что-то упущено.

1 Ответ

1 голос
/ 15 октября 2019

Я думаю, что пример, приведенный в документации, написан для Observable, который испускает только один раз, а затем завершается, например, http get. Предполагается, что если вы хотите получить больше данных, вы снова подпишетесь, что обнулит счетчик внутри genericRetryStrategy. Однако если теперь вы хотите применить эту же стратегию к долговременной наблюдаемой, поток которой не будет завершен, если она не выдаст ошибку (например, у вас с interval()), вам нужно будет изменить genericRetryStrategy()чтобы узнать, когда необходимо сбросить счетчик.

Это можно сделать несколькими способами, я привел простой пример в этом StackBlitz , основанном на том, что, как вы сказали, пыталисьвыполнить. Обратите внимание, что я также немного изменил вашу логику, чтобы она больше соответствовала тому, что, как вы сказали, вы пытались сделать, - это «2 успешных попытки и затем 2 неудачных попытки». Однако важные биты модифицируют объект ошибки, который добавляется в genericRetryStrategy(), чтобы сообщить текущее число неудачных попыток, чтобы он мог реагировать соответствующим образом.

Вот код, скопированный здесь для полноты:

import { timer, interval, Observable, throwError } from 'rxjs';
import { map, switchMap, tap, retryWhen, delayWhen, mergeMap, shareReplay, finalize, catchError } from 'rxjs/operators';

console.clear();

interface Err {
  status?: number;
  msg?: string;
  int: number;
}

export const genericRetryStrategy = ({
  maxRetryAttempts = 3,
  scalingDuration = 1000,
  excludedStatusCodes = []
}: {
  maxRetryAttempts?: number,
  scalingDuration?: number,
  excludedStatusCodes?: number[]
} = {}) => (attempts: Observable<any>) => {
  return attempts.pipe(
    mergeMap((error: Err) => {
      // i here does not reset and continues to increment?
      const retryAttempt = error.int;

      // if maximum number of retries have been met
      // or response is a status code we don't wish to retry, throw error
      if (
        retryAttempt > maxRetryAttempts ||
        excludedStatusCodes.find(e => e === error.status)
      ) {
        return throwError(error);
      }
      console.log(
        `Attempt ${retryAttempt}: retrying in ${retryAttempt *
          scalingDuration}ms`
      );
      // retry after 1s, 2s, etc...
      return timer(retryAttempt * scalingDuration);
    }),
    finalize(() => console.log('We are done!'))
  );
};

let int = 0;
let err: Err = {int: 0};
//emit value every 1s
interval(1000).pipe(
  map((val) => {
    if (val > 1) {
      //error will be picked up by retryWhen
      int++;
      err.msg = "equals 1";
      err.int = int;
      throw err;
    }
    if (val === 0 && int === 1) {
      err.msg = "greater than 2";
      err.int = 2;
      int=0;
      throw err;
    }
    return val;
  }),
  retryWhen(genericRetryStrategy({
    maxRetryAttempts: 3,
    scalingDuration: 1000,
    excludedStatusCodes: [],
  }))
).subscribe(val => {
  console.log(val)
});

Для меня это все еще очень важно, но без понимания проблемы, которую вы пытаетесь решить более глубоко, я не могу сейчас придумать более декларативный подход ...

...