RxJS Operators: catchError
Tags: rxjs, javascript, typescript, webdev
Published: 2023-06-16 05:30:00

RxJS is a JavaScript library that enables the creation of asynchronous and event-based programs. The main type is the `Observable` and a suite of functions and operators are provided to make it easier to work with the data coming through. This series will detail those operators (and a few stand-alone functions) and provide examples of their use.

In this post, we’re going to cover the `catchError` operator.

## What does it do?

This operator, as the name suggests, is used to catch errors that are thrown from our observable. This gives us the ability to add special handling for errors and, more importantly, control what happens with our subscription. 

## Example

Here we’re going to use the `interval` function and the `map` operator. The `interval` function will provide us with an observable outputting incrementing numbers. We’ll be using the `map` operator to throw an error when the value reaches 10:

```tsx
import { catchError, interval, map, of } from 'rxjs';

interval(1000)
    .pipe(
        map(count => {
            if (count === 10) {
                throw new Error('Error thrown at count of 10');
            }

            return count;
        }),
        catchError((error: Error) => {
            return of('Error handled: ' + error.message)
        }))
    .subscribe(x => {
        console.log(x);
    });
```

This gives us the output:

```bash
0
1
2
3
4
5
6
7
8
9
Error handled: Error thrown at count of 10
```

We’ll see the program end at this point. This is due to a quirk of using `interval` rather than being expected behaviour. When the subscription to `interval` ends it will stop outputting values and by default an error being thrown will end the subscription.

What do we do if we want the subscription to continue? Well, the `catchError` operator has us covered. In the example above we’ve passed `catchError` a function with a single parameter, error. The optional second parameter provides us with a reference to the originally subscribed observable.

Let’s see an example of this. We’re going to create an observable for this one to avoid the quirks of `interval`:

```tsx
import { Observable, Subject, catchError, map } from 'rxjs';

const provider$ = new Subject<number>();
let count = 0;

provider$
    .pipe(
        map((x) => {
            if ((x % 5) === 0) {
                throw new Error('Error thrown at multiple of 5');
            }

            return x;
        }),
        catchError((error: Error, caught: Observable<number>) => {
            console.error('Error handled: ' + error.message);
            return caught;
        }))
    .subscribe(x => {
        console.log(x);
    });

setInterval(() => {
    count++;

    provider$.next(count);
}, 1000);
```

It looks like there’s a lot more going on here, but  we’ve just implemented our own version of `interval`. Now the `map` operator will throw an error every time we reach a multiple of 5. The important change is in the `catchError` handling:

```tsx
catchError((error: Error, caught: Observable<number>) => {
    console.error('Error handled: ' + error.message);
    return caught;
}))
```

Our error handling now takes `caught`(the original observable), logs an error, and returns `caught`. This will just force our pipeline to resubscribe on error. So now we get:

```tsx
1
2
3
4
Error handled: Error thrown at multiple of 5
6
7
8
9
Error handled: Error thrown at multiple of 5
```

As we see, each time an error is thrown we just resubscribe and wait for the next value. There are a couple of things to watch out for with this approach though:

- `caught` will return the original subscription *with all the operators you’ve added*. This may be what you’re looking for, but if there’s something in your pipeline throwing the error (like we have above) it’s going to keep happening
- If you’re using a `BehaviorSubject` the resubscribe is going to attempt to reprocess the last value that was sent from the observable. Like the first point, if you’re pipeline caused this error you risk it happening again.

So just bear in mind the observable you’re returning. You don’t want to end up with:

```bash
Error handled: Error thrown at multiple of 5
Error handled: Error thrown at multiple of 5
Error handled: Error thrown at multiple of 5
Error handled: Error thrown at multiple of 5
Error handled: Error thrown at multiple of 5
Error handled: Error thrown at multiple of 5
```

Because the programming is repeatedly attempting to process a bad value.

The source code for this example is available on GitHub:
{% embed https://github.com/drownedintech/rxjs-operators %}