biswas
biswas

Reputation: 125

RxJs retryWhen throwing exception before all retry attempt

I'm trying to implement an api call that will be

  1. retried few times if there is any errors
  2. after a specific time delay
  3. with some other condition check like - if returned success response json has any field null, I'll retry the api call

I tried to implement what is mentioned here - Rxjs Retry with Delay function

Following is my code segment

Api call

    delay = 5000;
    retryCount = 5;
    return this.httpClient.post(http://localhost:8080/info,JSON.stringify(data))
    .pipe(
      retryWhen(errors => 
          errors.pipe(
            delay(this.delay), 
            take(this.retryCount),
            tap(val => {
              console.log('Retrying.');
            }),
            concatMap(() => Observable.throw(new Error('Retry limit exceeded!')))
          )
        )
    );

Processing the response

this.searchService.searchInfo(param1, param2).subscribe(data => { this.handleSuccessResponse(data) }, (error) => {
        if (error) {
          // Handle specific error here
          handleErrorResponse(error);
        }
});


handleSuccessResponse(data){
  // handle success response here
}

handleErrorResponse(error){
  // Handle generic error here
}

The issue I am getting, before retrying for 5 times as I mentioned in the code, the exception in the concatMap is getting throwed. What I am missing here?

I'm using RxJS 6.4 with Angular12

Upvotes: 1

Views: 1833

Answers (2)

Eddy Lin
Eddy Lin

Reputation: 683

Case 1

  1. If res === null always repeat (delay 500ms)

Repeat HTTP call until the desired value is returned with RxJs

  1. retry 5 times if API Error (delay 500ms)

Rxjs Retry with Delay function

  • RxJS 6.3 ⬆
// RxJS 6.x
api$.pipe(
    repeatWhen(delay(500)),
    skipWhile((res) => res === null),
    take(1),
    retryWhen(delayWhen((err, i) => i < 5 ? timer(500) : throwError(err)))
).subscribe(observer);
  • RxJS 7.x
// RxJS 7.x
api$.pipe(
    repeat({ delay: 500 }),
    skipWhile((res) => res === null),
    take(1),
    retry({ count: 5, delay: 500 })
).subscribe(observer);

Case 2

  1. res === null + API Error = 5 times (delay 500ms)
  • RxJS 6.3 ⬆
// RxJS 6.x
api$.pipe(
    skipWhile((res) => res === null),
    throwIfEmpty(),
    retryWhen(delayWhen((err, i) => i < 5 ? timer(500) : throwError(err)))
).subscribe(observer);
  • RxJS 7.x
// RxJS 7.x
api$.pipe(
    skipWhile((res) => res === null),
    throwIfEmpty(),
    retry({ count: 5, delay: 500 })
).subscribe(observer);

Upvotes: 2

Naren Murali
Naren Murali

Reputation: 58492

I went through the learnRxjs docs and got this generic method, which seems to satisfy your requirement!

import { Observable } from 'rxjs/Observable';
import { _throw } from 'rxjs/observable/throw';
import { timer } from 'rxjs/observable/timer';
import { mergeMap, finalize } from 'rxjs/operators';

export const genericRetryStrategy =
  ({
    maxRetryAttempts = 3,
    scalingDuration = 1000,
    excludedStatusCodes = [],
  }: {
    maxRetryAttempts?: number;
    scalingDuration?: number;
    excludedStatusCodes?: number[];
  } = {}) =>
  (attempts: Observable<any>) => {
    return attempts.pipe(
      mergeMap((error, i) => {
        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 > maxRetryAttempts ||
          excludedStatusCodes.find((e) => e === error.status)
        ) {
          return _throw('Retry limit exceeded!');
        }
        console.log(
          `Attempt ${retryAttempt}: retrying in ${
            retryAttempt * scalingDuration
          }ms`
        );
        // retry after 1s, 2s, etc...
        return timer(retryAttempt * scalingDuration);
      }),
      finalize(() => console.log('We are done!'))
    );
  };

forked stackblitz

Upvotes: 2

Related Questions