catchError: Handling Errors in RxJS Streams

When working with observables, catchError acts as a safety net for errors that occur in the source stream. Until an error is thrown, the operator simply forwards every emitted value to the observer. The moment an error occurs, however, a designated callback function is invoked with that error as its argument. This callback is expected to return a new observable, which then takes the place of the original source.

Once this replacement observable is returned, the operator subscribes to it and begins forwarding its emissions to the observer instead. If the replacement observable itself throws an error, that error notification is passed along to the observer just as it would be from any other source.

Because the callback receives not only the error but also the original observable that caused it, you have the option to retry the failed source by simply returning it again from the callback. This makes the operator quite flexible for recovery scenarios.

Here is the step-by-step flow of how catchError operates:

  1. Subscribe to the source observable
  2. Whenever a value is emitted from the source, forward it to the observer
  3. If the source emits an error, invoke the callback and subscribe to the observable it returns
  4. When the replacement observable emits a value, forward it to the observer
  5. When the replacement observable completes, send the complete notification to the observer
  6. If the replacement observable throws an error, send the error notification to the observer

When to Apply It

Errors are an inevitable part of any application, and RxJS is no different. A typical example is a failed network request — there are countless reasons why it might not succeed. You can handle such failures by passing an error handler to the observer when subscribing, and in some cases that is sufficient. However, that approach has its limitations.

With observer-based error handling, there is no way to recover from the error or substitute a fallback value in place of what the backend was supposed to return. This is precisely the gap that catchError fills.

Below is an example where catchError is used to attempt a secondary URL when the primary one fails:

const server1 = 'http://url-to-fail.com';
const server2 = 'https://api.mocki.io/v1/b043df5a';

fromFetch(server1).pipe(
   catchError((err) => fromFetch(server2))
).subscribe((res) => res.status);

Interactive Demonstration

Further Reading