RxJS: Error Handling With forkJoin

Error handling for combination static methods, like forkJoin, concat, and zip

João Ghignatti
Better Programming

--

If you’re an Angular developer, you’re already acquainted with RxJS, or, at least, you know that, after a service call with HttpClient, you should subscribe.

But, you can always achieve more with a deeper knowledge of reactive extensions.

There’s a handful of operators that, if used correctly, can make things easier, which is different from just knowing how to subscribe. That’s exactly what RxJS has to say about operators:

“RxJS is mostly useful for its operators, even though the observable is the foundation.”

Sometimes, when working with observables, you end up having to combine them. RxJS gives you combination operators and static methods that help you handle those cases, but life is not a bed of roses.

When we combine multiple observables, trying to handle errors becomes a real problem, RxJS methods mostly don’t behave as you’d want.

Let’s have a look at an example and the main reason why I’m writing this.

const todo1$ = this.myService.getTodo(1);
const error$ = this.myService.getTodo(201);
const todo2$ = this.myService.getTodo(2);
forkJoin([todo1$, error$, todo2$])
.subscribe(
next => console.log(next),
error => console.log(error)
);

In the sample code above, I’m using a service to store GET calls to JSONPlaceholder’s open API, each of them is supposed to return an observable of todo by its id.

Then, I combine those observables using forkJoin, which executes those calls in parallel.

Some may say that, if you need to combine requests, you’re doing it wrong or your back end is wrong.

Well, it can happen that the data you’re handling in the front end is actually from two different collections in your database, or maybe you need to execute a request to each one of the modified objects from a form, delete all actions, etc.

forkJoin will subscribe to the passed observables, hence making the HTTP GET requests.

According to info from JSONPlaceholder’s website, they have 200 to-dos, so I purposely have an error$ variable that calls for the todo with 201 as id.

This will return an error, but how will I receive the information when I subscribe?

Logged to console by forkJoin's onError

Checking the console, we see only an error message, captured and logged by the onError callback from subscribe.

Chrome DevTools network tab

But wait… If we check the network tab in our DevTools, we see that all three GET’s were called. todo1$ and todo2$ returned as success, so where are they?

Turns out this is the way forkJoin works, as per the RxJS docs:

“If any input observable errors at some point, forkJoin will error as well and all other observables will be immediately unsubscribed.”

So, you just lost the retrieved information from todo1$ and todo2$.

Once again, if you’re an Angular developer, this time using NgRx, maybe you already had to worry about not letting your observable die.

That’s because, when piping inside an Effect, if you don’t handle an error correctly, your Effect may die and not be executed if you dispatch the actions it handles again.

So, the key here is to individually handle errors, like for each observable.

Piping this observable with the catchError operator will handle the error and map it into another value. You can find the best way that fits your needs, but here are some examples:

const error$ = this.myService.getTodo(201)
.pipe(
catchError(err => of(err.status)),
);
...const error$ = this.myService.getTodo(201)
.pipe(
catchError(err => of({isError: true, error: err})),
);
...const error$ = this.myService.getTodo(201)
.pipe(
catchError(err => of(undefined)),
);

This way, once error$ throws an error, it’ll be handled internally and a new value will be returned to the outer observable. You can map the error into any other value as shown, the status code, undefined, a brand new object, etc.

Now, if we check the console, it’ll show an array containing two todo objects and a string, the status code from error$.

All this is handled by forkJoin.subscribe(next => {}), because forkJoin doesn’t know there was an error.

Logged to console by forkJoin's onNext

The same approach can be used with concat. As per its docs:

“Creates an output Observable which sequentially emits all values from given Observable and then moves on to the next.

If any Observable in chain errors, instead of passing control to the next Observable, concat will error immediately as well. Observables that would be subscribed after the one that emitted error, never will.”

concat([todo1$, error$, todo2$])
.subscribe(
next => console.log(next),
error => console.log(error)
);

If we do not apply our observable death-prevention, concat will dispatch the value of todo1$ and then error, not subscribing to todo2$.

If we internally handle the error$ error, that's our retuned value:

Logged to console by concat's onNext

Conclusion

Well, the key here is, as said, do not let your observable die.

The outer observable doesn’t need to know that an inner observable threw an error and you can return important data to find out which of them failed.

This is applicable to other cases, not only service calls, and other combination methods or even operators. When piping, you can end up with a higher-order observable and not handling its error can lead to a real headache.

I hope this helps.

--

--