Created
June 1, 2020 06:59
-
-
Save ikismail/c618e01a766fc8eb980e599e59a8381d to your computer and use it in GitHub Desktop.
Retry failed HTTP requests in Angular | Immediate retry, delayed retry and retry with backoff
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 1. Immediate retry | |
* 2. delayed retry | |
* 3. retry with backoff | |
*/ | |
import { Injectable } from "@angular/core"; | |
import { HttpClient } from "@angular/common/http"; | |
import { EMPTY, Observable, of, throwError } from "rxjs"; | |
import { | |
catchError, | |
retry, | |
shareReplay, | |
delay, | |
mergeMap, | |
retryWhen | |
} from "rxjs/operators"; | |
const getErrorMessage = (maxRetry: number) => | |
`Tried to load resource over XHR for ${maxRetry} times without success. Giving Up.`; | |
const DEFAULT_MAX_RETRIES: number = 5; | |
const DEFAULT_BACKOFF: number = 1000; | |
export function delayedRetry(delayMs: number, maxRetry = DEFAULT_MAX_RETRIES) { | |
let retries = maxRetry; | |
return (src: Observable<any>) => | |
src.pipe( | |
retryWhen((err: Observable<any>) => | |
err.pipe( | |
delay(delayMs), | |
mergeMap(error => | |
retries-- > 0 | |
? of(error) | |
: throwError(getErrorMessage(maxRetry)) | |
) | |
) | |
) | |
); | |
} | |
export function retryWithBackOff( | |
delayMs: number, | |
maxRetry = DEFAULT_MAX_RETRIES, | |
backOffMs = DEFAULT_BACKOFF | |
) { | |
let retries = maxRetry; | |
return (src: Observable<any>) => | |
src.pipe( | |
retryWhen((err: Observable<any>) => | |
err.pipe( | |
mergeMap(error => { | |
if (retries-- > 0) { | |
const backOffTime = | |
delayMs + (maxRetry - retries) * backOffMs; | |
return of(error).pipe(delay(backOffTime)); | |
} | |
return throwError(getErrorMessage(maxRetry)); | |
}) | |
) | |
) | |
); | |
} | |
@Injectable() | |
export class AlbumService { | |
private ALBUM_ENDPOINT = "http://localhost:3200"; | |
constructor(private http: HttpClient) {} | |
// Usually, In Angular we create a service, Inject the HttpClient and use it to get data from the backend | |
albums(): Observable<any[]> { | |
return this.http.get<any[]>(`${this.ALBUM_ENDPOINT}/albums`).pipe( | |
catchError(() => { | |
// Perform some error handling | |
return EMPTY; | |
}) | |
); | |
} | |
// 1. Using Immediate Retry | |
albums_immediate_retry(): Observable<any[]> { | |
return this.http.get<any[]>(`${this.ALBUM_ENDPOINT}/albums`).pipe( | |
retry(3), | |
catchError(() => { | |
// Perform some error handling | |
return EMPTY; | |
}), | |
shareReplay() | |
); | |
} | |
// 2. Using Delayed Retry | |
albums_with_delayed_retry(): Observable<any[]> { | |
return this.http.get<any[]>(`${this.ALBUM_ENDPOINT}/albums`).pipe( | |
delayedRetry(1000), | |
catchError(() => { | |
// Perform some error handling | |
return EMPTY; | |
}), | |
shareReplay() | |
); | |
} | |
// 3. retry with backoff | |
albums_with_retry_backoff(): Observable<any[]> { | |
return this.http.get<any[]>(`${this.ALBUM_ENDPOINT}/albums`).pipe( | |
retryWithBackOff(1000), | |
catchError(() => { | |
// Perform some error handling | |
return EMPTY; | |
}), | |
shareReplay() | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment