Onera
Onera

Reputation: 717

How to retry when the API calls fails after subscribing in Angular 2+

I am calling an API to get random details. The problem is that, sometimes I am getting 502 error as bad gateway and it may also break due to a bad network connection also. Below is my code to API call

// COMPONENT API CALL SUBSCRIBE
     this._service.post('randomAPI/getdetails', filters).subscribe((response: any) => {
     this.itemList = response;    
   });

// SHARED SERVICE
     post<T>(url: string, body: any): Observable<T> {
     return this.httpClient.post<T>(url, body);
   } 

Whenever I get 500 or 502 server error, using an Interceptor I am routing to a error page to notify the user as server issue.

Instead, can I make the API to try one more time in component level or interceptor level if it fails and then route to error page?

// INTERCEPTOR
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(catchError(error => {
        if (error.status === 401 || error.status === 400 || error.status === 403) {
            this.router.navigateByUrl('abort-access', { replaceUrl: true });
        } else if (error.status === 500 || error.status === 502) {
                this.router.navigateByUrl('server-error', { replaceUrl: true });
        }
        return throwError("error occured");
     }));
    }

I saw few examples as they are using pipe and adding retryWhen() to achieve this. But as I am very new to angular, I am not able to figure out a way to do it.

Could anyone please help?

Upvotes: 4

Views: 15280

Answers (2)

vikash vishwakarma
vikash vishwakarma

Reputation: 69

Make this function

           delayedRetry(delayMs:number,maxRetry:number){

            let retries=maxRetry;

            return (src:Observable<any>)=>
            src.pipe(
           retryWhen((errors:Observable<any>)=>errors.pipe(
           delay(delayMs),
           mergeMap(error =>retries -- > 0 ? 
           of(error):throwError("Retrying..."))
     ))
   )
  
}

call this inside api url

    return this.http.get<RouteData[]>(url).pipe(
    this.delayedRetry(10000,4),
    catchError(err => {
   console.error(err);
    return EMPTY;
    }),
  shareReplay()
  );

Upvotes: 1

Kurt Hamilton
Kurt Hamilton

Reputation: 13515

You can use the retryWhen operator. The principle behind this is that you throw an error when you don't want to retry.

retryWhen is effectively a fancy catchError that will automatically retry unless an error is thrown.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  return next.handle(request).pipe(
    // retryWhen operator should come before catchError operator as it is more specific
    retryWhen(errors => errors.pipe(
      // inside the retryWhen, use a tap operator to throw an error 
      // if you don't want to retry
      tap(error => {
        if (error.status !== 500 && error.status !== 502) {
          throw error;
        }
      })
    )),

    // now catch all other errors
    catchError(error => {     
      if (error.status === 401 || error.status === 400 || error.status === 403) {
        this.router.navigateByUrl('abort-access', { replaceUrl: true });
      }

      return throwError("error occured");
    })
  );
}

DEMO: https://stackblitz.com/edit/angular-qyxpds

Limiting retries

The danger with this is that you will perform continuous requests until the server doesn't return a 500 or 502. In a real-world app you would want to limit retries, and probably put some kind of delay in there to avoid flooding your server with requests.

To do this, you could use take(n) which will restrict your requests to n failed attempts. This won't work for you because take will stop the observable from proceeding to catchError, and you won't be able to perform navigation.

Instead, you can set a retry limit and throw an error once the retry limit has been reached.

const retryLimit = 3;
let attempt = 0;

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  return next.handle(request).pipe(
    // retryWhen operator should come before catchError operator as it is more specific
    retryWhen(errors => errors.pipe(
      tap(error => {
        if (++attempt >= retryLimit || (error.status !== 500 && error.status !== 502)) {
          throw error;
        }
      })  
    )),

    // now catch all other errors
    catchError(error => {     
      if (error.status === 401 || error.status === 400 || error.status === 403) {
        this.router.navigateByUrl('abort-access', { replaceUrl: true });
      } else if (error.status === 500 || error.status === 502) {
        this.router.navigateByUrl('server-error', { replaceUrl: true });
        // do not return the error
        return empty();
      }

      return throwError("error occured");
    })
  );
}

DEMO: https://stackblitz.com/edit/angular-ud1t7c

Upvotes: 5

Related Questions