Error Handling in RxJS
Errors in RxJS are fatal โ they stop the stream. catchError recovers, retry tries again, throwError re-throws. Build robust error handling with these operators.
In RxJS, errors are terminal events โ when an error occurs, the Observable stops emitting values.
"Error aaya toh stream band โ doosra pani nahi aayega."
import { Observable } from 'rxjs';
new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(new Error('Bhai nahi hua!')); // Error!
subscriber.next(3); // NEVER REACHED
subscriber.complete(); // NEVER REACHED
}).subscribe({
next: v => console.log('Value:', v),
error: e => console.error('Error:', e.message),
complete: () => console.log('Done'),
});
// Output:
// Value: 1
// Value: 2
// Error: Bhai nahi hua!
Key points about errors in RxJS:
- Errors are terminal โ stream dies immediately
- All subsequent .next() and .complete() calls are ignored
- Error propagates to subscriber's error callback
- If no error handler โ uncaught error โ app crash
- Use
catchErrorto recover,retryto try again
Think of it like a pipe โ if the pipe breaks, water stops flowing. You need to either fix the pipe (retry) or divert to another pipe (catchError).
catchError is the RxJS operator that catches an error and returns a fallback Observable โ the stream continues with the fallback.
import { catchError, of } from 'rxjs';
this.http.get('/api/biryani').pipe(
catchError(error => {
console.error('API failed:', error.message);
// Return fallback โ stream continues!
return of([]); // of() creates Observable that emits []
})
).subscribe(data => {
this.biryanis = data; // [] if API failed
});
"catchError = jugaad โ error aaya toh default value de do."
Critical rules for catchError:
// โ
CORRECT: Returns Observable
catchError(error => of([]))
// โ
CORRECT: Re-throw with throwError (RxJS 7+)
catchError(error => throwError(() => new CustomError(error)))
// โ WRONG: Returns value directly
catchError(error => []) // Error! Must return Observable!
// โ WRONG: Doesn't return anything
catchError(error => console.log(error)) // Stream dies!
catchError MUST return an Observable. Use of() for fallback value, EMPTY to silently complete, throwError to re-throw.
retry(n) resubscribes to the source Observable up to n times if it errors. This is useful for temporary network glitches.
import { retry, catchError, of } from 'rxjs';
this.http.get('/api/biryani').pipe(
retry(3), // Try 3 more times before giving up
catchError(error => {
console.error('All 3 retries failed:', error);
return of([]);
})
).subscribe(data => this.biryanis = data);
"retry = phir koshish karo โ 3 baar try karo, phir haath pair dhono."
retry behavior:
// Request 1: fails โ retry 1: fails โ retry 2: fails โ retry 3: fails โ catchError โ fallback
// Request 1: fails โ retry 1: succeeds! โ subscriber gets data (no more retries)
// retry(0): No retries โ just the original request
// retry(3): Up to 3 retries (4 total attempts including original)
// retry(): INFINITE retries โ dangerous!
โ ๏ธ ALWAYS use retry(n) with a number. Never use retry() without arguments โ it retries forever (infinite loop if server is permanently down).
For more advanced retry (with delay between attempts), use retryWhen or delay operator.
throwError creates an Observable that immediately errors. It's used to re-throw errors from catchError with more context.
import { throwError, catchError, of } from 'rxjs';
// Basic โ just throw an error
throwError(() => new Error('Nahi hua bhai')).subscribe({
error: e => console.error(e.message) // "Nahi hua bhai"
});
// With custom error context
catchError(originalError => {
// Log the original error for debugging
console.error('Original error:', originalError);
// Throw a more user-friendly error
return throwError(() => ({
message: 'Biryani load nahi hua, thodi der baad try karo',
code: 'BIRYANI_LOAD_FAILED',
original: originalError.message,
timestamp: new Date().toISOString()
}));
});
"throwError = error ko pack karke bhejo โ original + extra info."
throwError in RxJS 7+ (factory function):
// โ
NEW (RxJS 7+) โ factory function REQUIRED
throwError(() => new Error('fail'))
// โ OLD (RxJS 6) โ deprecated!
throwError(new Error('fail'))
The factory function () => error defers error creation and prevents stack trace pollution. Always use it.
A complete error handling strategy for Angular apps has multiple layers.
Layer 1: Service Level โ catchError + retry
// biryani.service.ts
getBiryani(id: number): Observable {
return this.http.get(`/api/biryani/${id}`).pipe(
retry(2), // Retry network glitches
catchError(error => {
if (error.status === 404) {
return of(null); // Not found โ return null gracefully
}
// Re-throw serious errors for global handler
return throwError(() => error);
})
);
}
Layer 2: Component Level โ Specific Error Handling
this.biryaniService.getBiryani(id).subscribe({
next: biryani => {
if (biryani === null) {
this.showNotFound = true; // Handle 404
} else {
this.biryani = biryani;
}
},
error: error => {
this.showServerError = true; // Handle 500 etc.
}
});
Layer 3: Global Level โ Interceptor
// Interceptor catches 401 โ redirect to login
// Catches 0 โ toast "Network error"
// Catches 500 โ toast "Server problem"
"Service level = local handling, Interceptor = global handling, dono saath mein."
Best practice: Service returns typed fallback or re-throws. Component decides what to show. Interceptor handles global concerns (auth, network).
Key Takeaways
- โ Errors in RxJS are terminal โ once error occurs, stream stops, no more values
- โ catchError(error => of(fallback)) โ must return Observable, recovers from error with fallback
- โ retry(n) โ resubscribes up to n times (for network glitches), ALWAYS set a limit
- โ throwError(() => customError) โ factory function REQUIRED in RxJS 7+
- โ Strategy: retry + catchError in service, specific handling in component, global in interceptor
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login