Retry a request for a number of times in Angular app

3k Views Asked by At

I have this situation where I need to make a get request. I do know how to use Angular's http client, but I need to make the request retry automatically if it fails, for a number of times before giving up and throwing an error, and also I need to put a delay between each retry.

For example, I need my request, if it fails, to retry for a total of 5 times, and between each retry have 10 seconds delay.

Is there an easy and clear way to do something like that?

2

There are 2 best solutions below

0
On BEST ANSWER

I have the same need in my angular app, so I created a pipeable operator called retryWithBackoff. It uses exponential backoff, so the time between retries looks like:

delayMs * Math.pow(2, retries)

It can be used very simply:

  getDiscussionList(): Observable<DiscussionListModel[]> | Observable<never> {
    return this.httpClient
      .get<DiscussionListModel[]>('/api/discussions').pipe(
        retryWithBackoff()
  );

Here is the operator:

export function retryWithBackoff<T>(
  delayMs: number = 1000,
  maxRetries: number = 5,
  maxTime: number = 12000
): MonoTypeOperatorFunction<T> {
  return (source: Observable<T>) => {
    const currentMs = new Date().getTime();
    return source.pipe(
      timeout(maxTime),
      retryWhen(errors => {
        let retries = 0;
        return errors.pipe(
          mergeMap(next => {
            // If we caught TimeoutError we must rethrow because retryWhen would retry
            // if request failed due to timeout.
            // The timeout(maxTime) isn't reached because a new request is sent
            // therefore we have to compare against currentMs
            if ((next instanceof TimeoutError)
              || (new Date()).getTime() >= currentMs + maxTime) {
              return throwError(new HttpTimeoutError());
            }
            retries++;
            if (retries >= maxRetries) {
              return throwError(new HttpMaxRetriesError());
            }
            return timer(delayMs * Math.pow(2, retries));
          }),
        );
      })
    );
  };
}

It will timeout in 12 seconds, regardless of how many attempts have been made. The two exceptions it can throw are just an empty custom exception class:

export class HttpEstablishError {}
export class HttpMaxRetriesError extends HttpEstablishError {}
export class HttpTimeoutError extends HttpEstablishError {}

I also have some incomplete jasmine tests for it:

describe('Utilities', () => {
  describe('retryWithBackoff should', () => {
    it('return success observable after failing max-1 times', (done) => {
      const source = (count, maxRetries) => {
        return defer(() => {
          if (count <= maxRetries) {
            count++;
            return throwError(true);
          } else {
            return of(true);
          }
        });
      };
      source(1, 5 - 1).pipe(retryWithBackoff(1, 5)).subscribe(
        (value) => {
          expect(value).toBe(true);
        },
        () => {
          fail();
          done();
        },
        () => {
          done();
        }
      );
    });

    it('raise HttpTimeoutError if maxTime is reached', (done) => {
      const maxTime = 1000;
      const source = new Subject<any>();
      source.pipe(retryWithBackoff(1000, 5, maxTime)).subscribe(
        () => {
          fail('should not happen');
        },
        (err) => {
          expect(err).toBeInstanceOf(HttpTimeoutError);
          done();
        }
      );
    });
  });

  it('raise HttpMaxRetriesError is maxRetries is reached', (done) => {
    const source = (count, maxRetries) => {
      return defer(() => {
        if (count <= maxRetries) {
          count++;
          return throwError(true);
        } else {
          return of(true);
        }
      });
    };
    source(1, 5 + 1).pipe(retryWithBackoff(1, 5)).subscribe(
      () => {
      },
      (err) => {
        expect(err).toBeInstanceOf(HttpMaxRetriesError);
        done();
      },
    );
  });
});
1
On

In angular rxj there an method called ‘retry(number)’ you can read more on (https://angular.io/guide/http):

    getConfig() {
       return this.http.get<Config>(this.configUrl).pipe(
              retry(3), // retry a failed request up to 3 times
              catchError(this.handleError) // then handle the error
       );}