14

I am trying to use an interceptor to handle http errors and retry for a special error status, in my case the status code 502.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        retryWhen(errors => {
          return errors
            .pipe(
              mergeMap(error => (error.status === 502) ? throwError(error) : of(error)),
              take(2)
            )
        })
      )
  }

But it's not working, whereas when I am using retry() in this fashion, it's working perfectly

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        retry(2),
        catchError((error: HttpErrorResponse) => {
          return throwError(error);
        })
      )
  }
2
  • 1
    you want to retry one every other code except 502? Commented Feb 17, 2019 at 12:17
  • No, I want to retry only if status is 502 Commented Feb 17, 2019 at 12:18

3 Answers 3

18

I took your approach and expanded it a little, out of own interest.

The first would be to create a sort of custom operator:

import { timer, throwError, Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

export interface RetryParams {
  maxAttempts?: number;
  scalingDuration?: number;
  shouldRetry?: ({ status: number }) => boolean;
}

const defaultParams: RetryParams = {
  maxAttempts: 3,
  scalingDuration: 1000,
  shouldRetry: ({ status }) => status >= 400
}

export const genericRetryStrategy = (params: RetryParams = {}) => (attempts: Observable<any>) => attempts.pipe(
  mergeMap((error, i) => {
    const { maxAttempts, scalingDuration, shouldRetry } = { ...defaultParams, ...params }
    const retryAttempt = i + 1;
    // if maximum number of retries have been met
    // or response is a status code we don't wish to retry, throw error
    if (retryAttempt > maxAttempts || !shouldRetry(error)) {
      return throwError(error);
    }
    console.log(`Attempt ${retryAttempt}: retrying in ${retryAttempt * scalingDuration}ms`);
    // retry after 1s, 2s, etc...
    return timer(retryAttempt * scalingDuration);
  })
);

You can then construct an interceptor based on this operator as follows:

@Injectable()
export class RetryInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const { shouldRetry } = this;
    return next.handle(request)
      .pipe(retryWhen(genericRetryStrategy({
        shouldRetry
      })));
  }

  private shouldRetry = (error) => error.status === 502;
}

You can see it working in this blitz

Sign up to request clarification or add additional context in comments.

Comments

5

I was trying to use Jota's response but retryWhen is now deprecated. So I've adapted his reponse using retry.

This is what I got:

  private readonly MAX_NUMBER_OF_RETRY_NO_CONNECTION: number = 5;
  private readonly RETRY_DELAY: number = 500;


  public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      retry({
        count: this.MAX_NUMBER_OF_RETRY_NO_CONNECTION,
        delay: (error: HttpErrorResponse, retryAttempt: number): Observable<number> => {
          // if maximum number of retries have been met
          // or response is a status code we don't wish to retry, throw error
          if (retryAttempt > this.MAX_NUMBER_OF_RETRY_NO_CONNECTION || error.status !== 404) {
            return throwError(() => error);
          }
          console.log(`Attempt ${retryAttempt}: retrying in ${retryAttempt * this.RETRY_DELAY}ms`);
          // retry after 1s, 2s, etc...
          return timer(retryAttempt * this.RETRY_DELAY);
        },
      }),
      catchError((error) => this.errorHandler(request, error))
    );
  }


  private errorHandler(request: HttpRequest<any>, error: HttpErrorResponse): Observable<HttpEvent<any>> {
    // Your usual error handler here....
  }

1 Comment

Thanks for updated version without deprecated operator.
0

Try the below code. You should do it the other way round.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        retryWhen(errors => {
          return errors
            .pipe(
              mergeMap(error=>error.status === 502?timer(0):throwError(error)),
              take(2)
            )
        }),
        catchError((error: HttpErrorResponse) => {
          return throwError(error);
        })
      )
  }

2 Comments

Still not working. What is the purpose of timer? I guess the problem is the error in mergeMap() is type of observable, not httpErrorResponse. So status is not a property of it and that's why it's always returning the error
Try use tap(console.log) to narrow down the problem. The code surely works as I have tried

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.