<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Front-end development

Handling Errors with RxJS

Adrian Ferguson

Handling errors gracefully gives your users a better experience. Learn useful RxJS error handling operators: catch and catchError, retry, and retryWhen.

posted in Angular, RxJS, best practices on February 22, 2022 by Adrian Ferguson


Handling Errors with RxJS

Adrian Ferguson by Adrian Ferguson

In Angular and other modern web frameworks, the UI is so intertwined that one unexpected response from the server can turn a beautiful website into a blank screen. Fortunately, you can build in error handling to help avoid this terrible scenario.

By error handling  I mean responding and recovering from errors that are likely an API returning an error or an unexpected response.

There are many JavaScript libraries you can use to handle errors. One of the most popular libraries is RxJS (short for Reactive Extensions Library for JavaScript).

RxJS Operators for Handling Errors

RxJS has operators designed to help you handle errors. Read on to learn these useful ones: catch and catchError, retry, and retryWhen.

catch and catchError are used in more general situations. retry and retryWhen can be geared more towards specific errors that need special handling.

catch and catchError

I'll start with catch and catchError. Here's how it works:

  1. It spots an error.
  2. It catches the error.
  3. You decide whether to process the error or just throw it out altogether.

To help you visualize this process, imagine working on an assembly line of chocolate-coated fruit candies. Your job is to make sure everything on the belt is candy. If it’s not candy, you should dip it in chocolate ("chocolatize" it) to turn it into candy. 

You’re operating your conveyor belt inspecting each item. Everything is fine until you spot a 🍓fruit🍓 that should be dipped into chocolate. Let's turn this into code.

In my example code below, const mapAndContinueOnError maps each value from an Observable. On an error,  catchError catches the error and returns an Observable of chocolatize fruit. I declare const candies for the values of Observable<string>. Then I have a check to throw an error when I see fruit 1, piping to mapAndContinueOnError.


import { throwError, of, from, iif, pipe } from 'rxjs';
import { catchError, map, flatMap } from 'rxjs/operators';

const mapAndContinueOnError = pipe(
  map((v) => v),
  catchError((err) => {
    console.log('Fruit has been chocolatized');
    //Return the chocolatize fruit
    return of('chocolatize fruit');
  })
);

//Release the candy!
const candies: Observable = from([
  'candy 1',
  'candy 2',
  'fruit 1',
  'candy 3',
  'candy 4',
]).pipe(
  flatMap((value) =>
    iif(
      () => value != 'fruit 1',
      of(value),
      throwError(new Error('Fruits need to be dipped in chocolate!'))
    ).pipe(mapAndContinueOnError)
  )
);

candies.subscribe((value) => console.log(value));
/**
 * Output:
 * 
 * candy 1
 * candy 2
 * Fruit has been chocolatized
 * chocolatize fruit
 * candy 3
 * candy 4
 */

retry

Next up is retry, which does exactly what it sounds like! Whenever there’s an error it will retry however many times you declare.

The code has const candies with the value ['candy 1', 'candy 2', 'fruit'] and I use mergeMap to go through each value within the object to find fruit and throw an error which will then be rerun twice to produced the output.

const candies: Observable = from(['candy 1', 'candy 2', 'fruit']).pipe(
  mergeMap(val => {
    if (val === 'fruit') {
      return throwError('Error!');
    }
    return of(val);
  }),
  // Get it to repeat twice
  retry(2)
);

candies.subscribe((value) => console.log(value), retry(2));

/**
 * Output: 
 * candy 1
 * candy 2
 * candy 1
 * candy 2
 * candy 1
 * candy 2
 */

retryWhen

retryWhen is similar to retrybut with a specified condition you define. Say you have a backend service you're making requests to and there's a 5xx error type you want to supply an arbitrary error message for. For instance, you’d be able to check the status code for errors starting with a 5

Example:

I have fakeBackend that I get data from. I want to retry getting the data if the status code of the error starts with 5 meaning any server error. I will keep retrying the code until I don’t get an error or until I don’t get an error code starting with 5.

const resp$ = fakeBackend.getData().pipe(
  retryWhen(errors =>
    errors.pipe(
      delay(1000),
      tap(err => {
        if (err.statusCode.startsWith('5')) {
          throw err;
        }

        console.log('Retrying...');
      })
    )
  )
);

resp$.subscribe({
  next: console.log
});

Conclusion

It's important to handle errors gracefully as a best practice. You want to enable the user to have an uninterrupted experience using your application. Within the Javascript ecosystem, RxJS has many useful tools to help you handle errors.

If you want to learn more about RxJS, be sure to check out our free Bitovi Academy course!

Create better web applications. We’ll help. Let’s work together.