catch / catchError

signature: catchError(project: (err: any, caught: Observable<T>) => ObservableInput<any>): Observable

Gracefully handle errors in an observable sequence.


💡 Need to retry a failed operation? Check out retry or retryWhen!

💡 For resource cleanup regardless of error, use finalize!

⚠ Remember to return an observable from the catchError function!


Why use catchError?

Think of catchError as your observable's safety net. When your stream encounters an error, whether from a failed HTTP request, unexpected data, or any other issue, catchError gives you a chance to recover gracefully instead of letting your entire observable sequence crash and burn. It's the difference between your app showing a friendly "Something went wrong" message versus a blank screen.

What makes catchError particularly valuable is its flexibility in how you respond to errors. You can provide fallback data from a cache, you can transform errors into user-friendly messages, or you can even decide to retry the operation (though retry is usually better for that). The key insight is understanding where you place catchError in your operator chain. Put it at the outer level and your entire stream ends when an error occurs, but place it inside operators like switchMap and only that inner operation fails while your stream continues.

In essence, catchError keeps your reactive applications resilient by ensuring that errors are handled on your terms, not left to crash your user experience.


Examples

Example 1: Catching error from observable

( StackBlitz )

// RxJS v6+
import { throwError, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

// emit error
const source = throwError('This is an error!');

// gracefully handle error, returning observable with error message
const example = source.pipe(
  catchError(val => of(`I caught: ${val}`))
);

// output: 'I caught: This is an error'
const subscribe = example.subscribe(val => console.log(val));

Example 2: Catching rejected promise

( StackBlitz )

// RxJS v6+
import { timer, from, of } from 'rxjs';
import { mergeMap, catchError } from 'rxjs/operators';

// create promise that immediately rejects
const myBadPromise = () =>
  new Promise((resolve, reject) => reject('Rejected!'));

// emit single value after 1 second
const source = timer(1000);

// catch rejected promise, returning observable containing error message
const example = source.pipe(
  mergeMap(_ =>
    from(myBadPromise()).pipe(
      catchError(error => of(`Bad Promise: ${error}`))
    )
  )
);

// output: 'Bad Promise: Rejected'
const subscribe = example.subscribe(val => console.log(val));

Example 3: Catching errors comparison when using switchMap/mergeMap/concatMap/exhaustMap

( StackBlitz )

// RxJS v6+
import { throwError, fromEvent, of } from 'rxjs';
import {
  catchError,
  tap,
  switchMap,
  mergeMap,
  concatMap,
  exhaustMap
} from 'rxjs/operators';

// simulate an API call that throws an error
const fakeRequest$ = of().pipe(
  tap(_ => console.log('fakeRequest')),
  throwError
);

/*
 * Placement of catchError MATTERS!
 *
 * When catchError is placed INSIDE the switchMap, only the inner
 * observable errors out and the outer stream continues.
 * You can keep clicking and making new requests.
 */
const iWillContinueListening$ = fromEvent(
  document.getElementById('continued'),
  'click'
).pipe(
  switchMap(_ => 
    fakeRequest$.pipe(
      catchError(_ => of('keep on clicking!!!'))
    )
  )
);

/*
 * When catchError is placed at the OUTER level, after switchMap,
 * the entire stream errors out and terminates.
 * After the first click, the stream is dead and won't respond to more clicks.
 */
const iWillStopListening$ = fromEvent(
  document.getElementById('stopped'),
  'click'
).pipe(
  switchMap(_ => fakeRequest$),
  catchError(_ => of('no more requests!!!'))
);

iWillContinueListening$.subscribe(console.log);
iWillStopListening$.subscribe(console.log);

Example 4: Providing fallback data on HTTP error

( StackBlitz )

// RxJS v6+
import { ajax } from 'rxjs/ajax';
import { of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

// attempt to fetch user data from API
const userData$ = ajax.getJSON('https://api.example.com/user/123').pipe(
  map(response => response.data),
  // if the request fails, provide cached or default data
  catchError(error => {
    console.error('Failed to fetch user, using cached data', error);
    return of({ 
      id: 123, 
      name: 'Cached User', 
      status: 'offline' 
    });
  })
);

// user always gets data, even if the API is down
userData$.subscribe(user => console.log('User:', user));


Additional Resources


📁 Source Code: https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/operators/catchError.ts

Last updated