retry or fallback mechanisms with error handling
To implement retry or fallback mechanisms with error handling in Angular, you can use RxJS operators like retry, retryWhen, and catchError. These operators allow you to handle errors gracefully and retry failed HTTP requests or provide fallback values.
1. Using retry for Automatic Retries
The retry operator retries a failed HTTP request a specified number of times before throwing an error.
- Description: Automatically retries the observable a specified number of times when an error occurs.
- Use Case: Use
retrywhen you want to retry a failed observable a fixed number of times without any custom logic. - Syntax:
retry(count: number): MonoTypeOperatorFunction<T>count: The number of retry attempts.
Example
import { of } from 'rxjs';
import { map, retry } from 'rxjs/operators';
let attempt = 0;
of('data')
.pipe(
map(() => {
attempt++;
if (attempt < 3) {
throw new Error('Error occurred');
}
return 'Success';
}),
retry(2) // Retry up to 2 times
)
.subscribe({
next: (value) => console.log(value),
error: (err) => console.error('Final Error:', err.message),
});
Output
Error occurred
Error occurred
Success
- Explanation:
- The observable retries twice after the first error.
- On the third attempt, it succeeds and emits
"Success". - If all retries fail, the error is propagated.
Realtime Example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.apiUrl).pipe(
retry(3), // Retry up to 3 times
catchError((error) => {
console.error('Error occurred:', error);
throw error; // Re-throw the error after retries
})
);
}
}
2. Using retryWhen for Conditional Retries
The retryWhen operator allows you to retry based on custom logic, such as waiting for a delay before retrying.
- Description: Retries the observable based on a custom logic defined in a notifier function. The notifier function receives an observable of errors and returns an observable that determines when to retry or terminate.
- Use Case: Use
retryWhenwhen you need custom retry logic, such as adding delays, exponential backoff, or stopping retries based on specific conditions. - Syntax:
retryWhen(notifier: (errors: Observable<any>) => Observable<any>): MonoTypeOperatorFunction<T>
Example
import { of, timer } from 'rxjs';
import { map, retryWhen, delayWhen } from 'rxjs/operators';
let attempt = 0;
of('data')
.pipe(
map(() => {
attempt++;
if (attempt < 3) {
throw new Error('Error occurred');
}
return 'Success';
}),
retryWhen((errors) =>
errors.pipe(
delayWhen(() => timer(1000)) // Wait 1 second before retrying
)
)
)
.subscribe({
next: (value) => console.log(value),
error: (err) => console.error('Final Error:', err.message),
});
Output
Error occurred (after 1 second)
Error occurred (after 1 second)
Success
- Explanation:
- The
retryWhenoperator uses theerrorsobservable to determine when to retry. - In this example, it waits 1 second before each retry.
- On the third attempt, it succeeds and emits
"Success".
- The
Realtime Example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError, timer } from 'rxjs';
import { retryWhen, catchError, delayWhen } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.apiUrl).pipe(
retryWhen((errors) =>
errors.pipe(
delayWhen(() => timer(2000)), // Wait 2 seconds before retrying
catchError((error) => {
console.error('Retries exhausted:', error);
return throwError(() => new Error('Failed after retries'));
})
)
),
catchError((error) => {
console.error('Error occurred:', error);
throw error;
})
);
}
}
3. Providing a Fallback Value
The catchError operator can be used to provide a fallback value when an error occurs.
Example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.apiUrl).pipe(
catchError((error) => {
console.error('Error occurred:', error);
return of({ fallbackData: true }); // Provide fallback data
})
);
}
}
4. Combining Retry and Fallback
You can combine retry and catchError to retry a request and provide fallback data if all retries fail.
Example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.apiUrl).pipe(
retry(3), // Retry up to 3 times
catchError((error) => {
console.error('Error occurred after retries:', error);
return of({ fallbackData: true }); // Provide fallback data
})
);
}
}
5. Using finalize for Cleanup
The finalize operator can be used to perform cleanup actions, such as hiding a loading spinner, regardless of whether the request succeeds or fails.
Example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { retry, catchError, finalize } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
console.log('Loading started');
return this.http.get(this.apiUrl).pipe(
retry(3),
catchError((error) => {
console.error('Error occurred:', error);
throw error;
}),
finalize(() => {
console.log('Loading finished'); // Cleanup action
})
);
}
}
6. Example with Multiple HTTP Calls
If you have multiple dependent HTTP calls, you can use switchMap or mergeMap with retry and fallback mechanisms.
Example:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { switchMap, retry, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl1 = 'https://api.example.com/data1';
private apiUrl2 = 'https://api.example.com/data2';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.apiUrl1).pipe(
retry(3),
switchMap((response1: any) => {
const dependentId = response1.id;
return this.http.get(`${this.apiUrl2}/${dependentId}`);
}),
catchError((error) => {
console.error('Error occurred:', error);
return of({ fallbackData: true });
})
);
}
}
7. Summary
- Retry Mechanisms:
- Use
retryfor simple retries. - Use
retryWhenfor conditional retries with delays.
- Use
- Fallback Mechanisms:
- Use
catchErrorto provide fallback data or handle errors gracefully.
- Use
- Cleanup:
- Use
finalizeto perform cleanup actions like hiding a loading spinner.
- Use
- Chaining HTTP Calls:
- Use
switchMapormergeMapfor dependent HTTP calls with retry and fallback.
- Use
This approach ensures robust error handling and improves the reliability of your Angular application.