NGRX: перехватчик попыток http, вызывающий сбой действия, которое не должно быть запущено - PullRequest
2 голосов
/ 29 марта 2020

Я использую NGRX и хочу, чтобы простые GET запросы к API были повторены пять раз. Причина этого в том, что я использую Azure Cosmos-DB и меня иногда душат. (свободный уровень).

Я создал для этого http-перехватчик, который довольно прост и выглядит так

@Injectable()
export class HttpRetryInterceptor implements HttpInterceptor {
  public intercept(
    request: HttpRequest<any>,
    httpHandler: HttpHandler
  ): Observable<HttpEvent<any>> {
    const nextRequest = request.clone();

    // here the NGRX failure action is not being triggered after five failing requests
    return httpHandler.handle(nextRequest).pipe(
      retryWhen(errors => errors.pipe(delay(1000), take(5))),
      last()
    );
  }
}

Это работает просто отлично, и каждый неудачный http-запрос повторяется пять раз с задержкой 1000 мс.

Проблема в том, что действие сбоя в эффекте не запускается, когда запросы фактически не выполняются пять раз.

load$ = createEffect(() =>
  this.actions$.pipe(
    ofType(globalStatsActions.load),
    mergeMap(() =>
      this.globalStatsService.load().pipe(
        map(stats =>
          globalStatsActions.loaded({
            latestStats: stats
          })
        ),
        catchError(() => of(globalStatsActions.loadFailed())) // not being called using the http-interceptor
      )
    )
  )
);

Что странно, что когда http-перехватчик использует оператор retry вместо retryWhen, он работает просто отлично. К сожалению, с этим оператором вы не можете определить delay, что требуется в моем случае.

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

// here the failure action is being triggered after five failing requests
public load(): Observable<GlobalStats> {
  return this.http.get<GlobalStats>(`${this.baseUrl}stats`)
  .pipe(
    retryWhen(errors => errors.pipe(delay(1000), take(5))),
    last()
  );

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

Я не могу найти причину этого, и я надеюсь, что кто-нибудь там знает, почему это не работает.

1 Ответ

3 голосов
/ 29 марта 2020

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

retry(n) просто пройдет вместе с уведомлением об ошибке при достижении n попыток. При попытках <<code>n он отписывается от источника, а затем повторно подписывается .

retryWhen(fn) поддерживает внутреннюю подписку, которая является результатом наблюдаемой обеспечивается функцией fn. Единственный аргумент fn - это субъект ошибок , который будет принимать значения sh каждый раз, когда возникает ошибка.

Итак, если у вас есть

retryWhen(subjectErrors => subjectErrors.pipe(a(), b()))

по сути такой же, как:

const subjectErrors = new Subject();
subjectErrors.pipe(a(), b()).subscriber(innerSubscriber);

* * * * * * * * * * * * * * * * * * * * * * * innerSubscriber * * * * * также используется в retryWhen, и его основная роль заключается в том, чтобы сообщать, когда приходит значение (next notification).

Когда это произойдет, источник будет переподписан . В этом случае источником является наблюдаемая, которая отправляет http-запрос, а при возникновении ошибки отправляет уведомление error; если запрос выполнен успешно, он выдаст уведомление next, а затем уведомление complete.

take(5) от retryWhen(errors => errors.pipe(delay(1000), take(5))) означает, что при достижении 5 попыток innerSubscriber (та, что сверху) выдаст уведомление complete.

Тогда у вас будет last с значением по умолчанию . Это означает, что когда он получает уведомление complete, без получения каких-либо уведомлений next ранее, он выдаст ошибку (поведение по умолчанию).

Тогда ошибка будет зафиксирована catchError() в вашем действии. Так что это должно объяснить, почему этот подход работает.

Что странно, это то, что когда http-перехватчик использует оператор, а не повторяет попытку, когда он работает просто отлично.

Это то, что я не могу объяснить, потому что, используя информацию из предыдущего раздела, она должна работать нормально, созданные потоки должны быть идентичны.

Если у вас есть

load () {
 return this.http.get(...);
}

и предполагая, что перехватчик - единственный, который вы используете, в

mergeMap(() =>
  this.globalStatsService.load().pipe(
    map(stats =>
      globalStatsActions.loaded({
        latestStats: stats
      })
    ),
    catchError(() => of(globalStatsActions.loadFailed())) // not being called using the http-interceptor
  )
)

this.globalStatsService.load() должен быть такой же, как

of(request).pipe(
  concatMap(
    req => new Observable(s => { /* making the request.. */ }).pipe(
      retryWhen(errors => errors.pipe(delay(1000), take(5))),
      last(),
    )
  )
)

, который должен быть таким же, как в случае с предыдущий раздел.

Так что, если я ничего не пропустил, нужно набрать catchError.

Если нет, это может означать, что у вас есть какой-то другой catchError или last получил значение и поэтому не выдает ошибку.

В этом случае что-то регистрируется?

    // here the NGRX failure action is not being triggered after five failing requests
    return httpHandler.handle(nextRequest).pipe(
      retryWhen(errors => errors.pipe(delay(1000), take(5))),
      tap(console.log), 
      last()
    );

Редактировать

Как видно из исходный код , наблюдаемый в котором запрос будет Я отправляю через своего подписчика событие, которое указывает, что запрос был отправлен.

Имея это в виду, вы можете использовать оператор filter:

return httpHandler.handle(nextRequest).pipe(
  filter(ev => ev.type !== 0),
  retryWhen(errors => errors.pipe(delay(1000), take(5))),
  last()
);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...