How to handle and catch errors in Rxjs

Few ways to handle the errors with Rxjs

ยท

4 min read

In Rxjs, when we work with observables handling the errors is a bit confusing for beginners because you can think of a try-catch, but Rxjs comes with operators to manage it, so what can I use and when?

Let's move to each step with code, the example uses angular httpClient, but it applies to any data stream.

๐Ÿ‘‰๐Ÿฝ Why pay for hosting? Click here to deploy your Angular apps for free on a reliable VPS

The scenario

Our app uses a service to get the list of beers and show the first one as the title.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable()
export class BeerService {
  private apiUrl = 'https://api.punkapi.com/v2/beers';
  constructor(private HTTP: HttpClient) {}

  getBeers(): Observable<any> {
    return this.http.get(this.apiUrl);
  }
}

The app component subscribes to it, shows the beer list, and takes the first one.

import { Component, OnInit } from '@angular/core';
import { BeerService } from './beer.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  title = 'my first beer';
  beers = [];
  constructor(private beerService: BeerService) {}

  ngOnInit() {
    try {
      this.beerService.getBeers().subscribe((beers) => {
        console.log(beers);
        this.beers = beers;
        this.title = beers[0].name;
      });
    } catch (err) {
      this.title = 'Ups a error';
    }
  }
}

What happens if the API fails? , We changed the URL to a failed URL to catch the error with some strategies.

Using try-catch

In JavaScript, we use a try-catch to validate a piece of code, and if something comes with an error, it catches.

But the try-catch is useless with our rxjs code because the errors happen in the subscribe scope, so try-catch doesn't solve anything, so we need to use Rxjs operators.

export class AppComponent implements OnInit {
  title = 'my first beer';
  beers = [];
  constructor(private beerService: BeerService) {}

  ngOnInit() {
    try {
      this.beerService.getBeers().subscribe((beers) => {
        console.log(beers);
        this.beers = beers;
        this.title = beers[0].name;
      });
    } catch (err) {
      this.title = 'Us a error';
    }
  }
}

Read more about try-cath

So, who is to catch the error in the subscription?

To understand why it is not working, remember that when we subscribe to an observable, the subscribe call takes three optional arguments.

      this.beerService
      .getBeers()
      .subscribe({
        next: (beers) => {
          console.log(beers);
          this.beers = beers;
          this.title = beers[0].name;
        },
        error: (e) => {
          console.log(e);
          this.title = 'ups';
        },
        complete: () => console.log('done'),
      });
  • next or success function is called each time the stream emits a value.

  • error: is a function called when an error occurs and gets the error.

  • complete: is a function that gets called only if the stream completes

So the error is in the subscribe function scope, so how can we manage the case?

Using Rxjs Operators

Rxjs provide some operators to help us with the errors, each of them is used in the scenario, so let's use each.

We are going to play with cathError, throwError, and EMPTY.

cathError

It catches the error, but emits the value. In short, it takes the error and returns another observable.

I removed the previous strategy about three callback functions and used the pipe to work with the catchError operator.

When the API fails, I return an array with the default beer Observable object.

Learn more about pipe

this.beerService
      .getBeers()
      .pipe(catchError(() => of([{ name: 'my default beer' }])))
      .subscribe((beers) => {
        console.log(beers);
        this.beers = beers;
        this.title = beers[0].name;
      });

The catchError is perfect for emitting a default value if something happens in our code, and to subscribe can take the default value as an emission.

throwError

Sometimes we don't want to emit the error but want to notify the error; for those scenarios, the throwError helps us.

throwError does not emit the data to the next, and it uses the error on the subscriber callbacks. If we want to catch a custom error or notify the backend, we can use the error callback in the subscriber.

 ngOnInit() {
    this.beerService
      .getBeers()
      .pipe(
        catchError(() => {
          return throwError(() => new Error('ups something happened));
        })
      )
      .subscribe({
        next: (beers) => {
          console.log(beers);
          this.beers = beers;
          this.title = beers[0].name;
        },
        error: (err) => {
          console.log(err);
        },
      });
  }

Read more about throwError

EMPTY

Sometimes we don't want to propagate the error in our component. Rxjs provide an EMPTY constant and return an empty Observable without emitting any data to the subscriber callbacks.

this.beerService
      .getBeers()
      .pipe(
        catchError(() => {
          return EMPTY;
        })
      )
      .subscribe({
        next: (beers) => {
          this.beers = beers;
          this.title = beers[0].name;
        },
        error: (err) => console.log(err),
      });

Read more about EMPTY

Conclusion

In short, we learn how to pipe the data and catch the errors using catchError, to modify the return observable or use EMPTY not to trigger the error to the component.

Play with the code in:

ย