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:
- It spots an error.
- It catches the error.
- 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 retry
but 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!
Previous Post