Dany Paredes
Dany Paredes | Javascript / Web

Dany Paredes | Javascript / Web

How to handle and catch errors in Rxjs

Photo by John Torcasio on Unsplash

How to handle and catch errors in Rxjs

Few ways to handle the errors with Rxjs

Dany Paredes's photo
Dany Paredes
·Mar 11, 2022·

4 min read

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Table of contents

  • The scenario
  • Using try-cath
  • So who to catch the error in the subscription?
  • Using Rxjs Operators
  • Conclusion

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 came with operators to manage it, so what I can use and when?

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

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 beers 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 change the URL to a failed URL, to catch the error with some strategies.

Using try-cath

In javascript, we use a try-catch to validate a piece of code, and if something came with an error it cath.

But the try-cath 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 to catch the error in the subscription?

To understand why is not working, first, remember 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 we can manage the case?

Using Rxjs Operators

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

We 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 the 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, 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 sommething happend'));
        })
      )
      .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 EMPTY constant and returns an empty Observable, without emit 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 to not trigger the error to the component.

Feel free to play with the code in stackbliz

Photo by John Torcasio on Unsplash

 
Share this