Error handling is an essential part of RxJs, as we will need it in just about any reactive program that we write.
Error handling in RxJS is likely not as well understood as other parts of the library, but it’s actually quite simple to understand if we focus on understanding first the Observable contract in general.
In this post, we are going to provide a complete guide containing the most common error handling strategies that you will need in order to cover most practical scenarios, starting with the basics (the Observable contract).
Table Of Contents
In this post, we will cover the following topics:
- The Observable contract and Error Handling
- RxJs subscribe and error callbacks
- The catchError Operator
- The Catch and Replace Strategy
- throwError and the Catch and Rethrow Strategy
- Using catchError multiple times in an Observable chain
- The finalize Operator
- The Retry Strategy
- Then retryWhen Operator
- Creating a Notification Observable
- Immediate Retry Strategy
- Delayed Retry Strategy
- The delayWhen Operator
- The timer Observable creation function
- Running Github repository (with code samples)
- Conclusions
So without further ado, let’s get started with our RxJs Error Handling deep dive!
The Observable Contract and Error Handling
In order to understand error handling in RxJs, we need to first understand that any given stream can only error out once. This is defined by the Observable contract, which says that a stream can emit zero or more values.
The contract works that way because that is just how all the streams that we observe in our runtime work in practice. Network requests can fail, for example.
A stream can also complete, which means that:
- the stream has ended its lifecycle without any error
- after completion, the stream will not emit any further values
As an alternative to completion, a stream can also error out, which means that:
- the stream has ended its lifecycle with an error
- after the error is thrown, the stream will not emit any other values
Notice that completion or error are mutually exclusive:
- if the stream completes, it cannot error out afterwards
- if the streams errors out, it cannot complete afterwards
Notice also that there is no obligation for the stream to complete or error out, those two possibilities are optional. But only one of those two can occur, not both.
This means that when one particular stream errors out, we cannot use it anymore, according to the Observable contract. You must be thinking at this point, how can we recover from an error then?
RxJs subscribe and error callbacks
To see the RxJs error handling behavior in action, let’s create a stream and subscribe to it. Let’s remember that the subscribe call takes three optional arguments:
- a success handler function, which is called each time that the stream emits a value
- an error handler function, that gets called only if an error occurs. This handler receives the error itself
- a completion handler function, that gets called only if the stream completes
Completion Behavior Example
If the stream does not error out, then this is what we would see in the console:
HTTP response {payload: Array(9)}
HTTP request completed.
As we can see, this HTTP stream emits only one value, and then it completes, which means that no errors occurred.
But what happens if the stream throws an error instead? In that case, we will see the following in the console instead:
As we can see, the stream emitted no value and it immediately errored out. After the error, no completion occurred.
Limitations of the subscribe error handler
Handling errors using the subscribe call is sometimes all that we need, but this error handling approach is limited. Using this approach, we cannot, for example, recover from the error or emit an alternative fallback value that replaces the value that we were expecting from the backend.
Let’s then learn a few operators that will allow us to implement some more advanced error handling strategies.
The catchError Operator
In synchronous programming, we have the option to wrap a block of code in a try clause, catch any error that it might throw with a catch block and then handle the error.
Here is what the synchronous catch syntax looks like:
This mechanism is very powerful because we can handle in one place any error that happens inside the try/catch block.
The problem is, in Javascript many operations are asynchronous, and an HTTP call is one such example where things happen asynchronously.
RxJs provides us with something close to this functionality, via the RxJs catchError Operator.
How does catchError work?
As usual and like with any RxJs Operator, catchError is simply a function that takes in an input Observable, and outputs an Output Observable.
With each call to catchError, we need to pass it a function which we will call the error handling function.
The catchError operator takes as input an Observable that might error out, and starts emitting the values of the input Observable in its output Observable.
If no error occurs, the output Observable produced by catchError works exactly the same way as the input Observable.
What happens when an error is thrown?
However, if an error occurs, then the catchError logic is going to kick in. The catchError operator is going to take the error and pass it to the error handling function.
That function is expected to return an Observable which is going to be a replacement Observable for the stream that just errored out.
Let’s remember that the input stream of catchError has errored out, so according to the Observable contract we cannot use it anymore.
This replacement Observable is then going to be subscribed to and its values are going to be used in place of the errored out input Observable.
The Catch and Replace Strategy
Let’s give an example of how catchError can be used to provide a replacement Observable that emits fallback values:
Let’s break down the implementation of the catch and replace strategy:
- we are passing to the catchError operator a function, which is the error handling function
- the error handling function is not called immediately, and in general, it’s usually not called
- only when an error occurs in the input Observable of catchError, will the error handling function be called
- if an error happens in the input stream, this function is then returning an Observable built using the
of([])
function - the
of()
function builds an Observable that emits only one value ([]
) and then it completes - the error handling function returns the recovery Observable (
of([])
), that gets subscribed to by the catchError operator - the values of the recovery Observable are then emitted as replacement values in the output Observable returned by catchError
As the end result, the http$
Observable will not error out anymore! Here is the result that we get in the console:
HTTP response []
HTTP request completed.
As we can see, the error handling callback in subscribe()
is not invoked anymore. Instead, here is what happens:
- the empty array value
[]
is emitted - the
http$
Observable is then completed
As we can see, the replacement Observable was used to provide a default fallback value ([]
) to the subscribers of http$
, despite the fact that the original Observable did error out.
Notice that we could have also added some local error handling, before returning the replacement Observable!
And this covers the Catch and Replace Strategy, now let’s see how we can also use catchError to rethrow the error, instead of providing fallback values.
The Catch and Rethrow Strategy
Let’s start by noticing that the replacement Observable provided via catchError can itself also error out, just like any other Observable.
And if that happens, the error will be propagated to the subscribers of the output Observable of catchError.
This error propagation behavior gives us a mechanism to rethrow the error caught by catchError, after handling the error locally. We can do so in the following way:
Catch and Rethrow breakdown
Let’s break down step-by-step the implementation of the Catch and Rethrow Strategy:
- just like before, we are catching the error, and returning a replacement Observable
- but this time around, instead of providing a replacement output value like
[]
, we are now handling the error locally in the catchError function - in this case, we are simply logging the error to the console, but we could instead add any local error handling logic that we want, such as for example showing an error message to the user
- We are then returning a replacement Observable that this time was created using throwError
- throwError creates an Observable that never emits any value. Instead, it errors out immediately using the same error caught by catchError
- this means that the output Observable of catchError will also error out with the exact same error thrown by the input of catchError
- this means that we have managed to successfully rethrow the error initially thrown by the input Observable of catchError to its output Observable
- the error can now be further handled by the rest of the Observable chain, if needed
If we now run the code above, here is the result that we get in the console:
As we can see, the same error was logged both in the catchError block and in the subscription error handler function, as expected.
Using catchError multiple times in an Observable chain
Notice that we can use catchError multiple times at different points in the Observable chain if needed, and adopt different error strategies at each point in the chain.
We can, for example, catch an error up in the Observable chain, handle it locally and rethrow it, and then further down in the Observable chain we can catch the same error again and this time provide a fallback value (instead of rethrowing):
If we run the code above, this is the output that we get in the console:
As we can see, the error was indeed rethrown initially, but it never reached the subscribe error handler function. Instead, the fallback []
value was emitted, as expected.
The Finalize Operator
Besides a catch block for handling errors, the synchronous Javascript syntax also provides a finally block that can be used to run code that we always want executed.
The finally block is typically used for releasing expensive resources, such as for example closing down network connections or releasing memory.
Unlike the code in the catch block, the code in the finally block will get executed independently if an error is thrown or not:
RxJs provides us with an operator that has a similar behavior to the finally functionality, called the finalize Operator.
Note: we cannot call it the finally operator instead, as finally is a reserved keyword in Javascript
Finalize Operator Example
Just like the catchError operator, we can add multiple finalize calls at different places in the Observable chain if needed, in order to make sure that the multiple resources are correctly released:
Let’s now run this code, and see how the multiple finalize blocks are being executed:
Notice that the last finalize block is executed after the subscribe value handler and completion handler functions.
The Retry Strategy
As an alternative to rethrowing the error or providing fallback values, we can also simply retry to subscribe to the errored out Observable.
Let’s remember, once the stream errors out we cannot recover it, but nothing prevents us from subscribing again to the Observable from which the stream was derived from, and create another stream.
Here is how this works:
- we are going to take the input Observable, and subscribe to it, which creates a new stream
- if that stream does not error out, we are going to let its values show up in the output
- but if the stream does error out, we are then going to subscribe again to the input Observable, and create a brand new stream
When to retry?
The big question here is, when are we going to subscribe again to the input Observable, and retry to execute the input stream?
- are we going to retry that immediately?
- are we going to wait for a small delay, hoping that the problem is solved and then try again?
- are we going to retry only a limited amount of times, and then error out the output stream?
In order to answer these questions, we are going to need a second auxiliary Observable, which we are going to call the Notifier Observable. It’s the Notifier
Observable that is going to determine when the retry attempt occurs.
The Notifier Observable is going to be used by the retryWhen Operator, which is the heart of the Retry Strategy.
RxJs retryWhen Operator Marble Diagram
To understand how the retryWhen Observable works, let’s have a look at its marble diagram:
Notice that the Observable that is being re-tried is the 1-2 Observable in the second line from the top, and not the Observable in the first line.
The Observable on the first line with values r-r is the Notification Observable, that is going to determine when a retry attempt should occur.
Breaking down how retryWhen works
Let’s break down what is going in this diagram:
- The Observable 1-2 gets subscribed to, and its values are reflected immediately in the output Observable returned by retryWhen
- even after the Observable 1-2 is completed, it can still be re-tried
- the notification Observable then emits a value
r
, way after the Observable 1-2 has completed - The value emitted by the notification Observable (in this case
r
) could be anything - what matters is the moment when the value
r
got emitted, because that is what is going to trigger the 1-2 Observable to be retried - the Observable 1-2 gets subscribed to again by retryWhen, and its values are again reflected in the output Observable of retryWhen
- The notification Observable is then going to emit again another
r
value, and the same thing occurs: the values of a newly subscribed 1-2 stream are going to start to get reflected in the output of retryWhen - but then, the notification Observable eventually completes
- at that moment, the ongoing retry attempt of the 1-2 Observable is completed early as well, meaning that only the value 1 got emitted, but not 2
As we can see, retryWhen simply retries the input Observable each time that the Notification Observable emits a value!
Now that we understand how retryWhen works, let’s see how we can create a Notification Observable.
Creating a Notification Observable
We need to create the Notification Observable directly in the function passed to the retryWhen operator. This function takes as input argument an Errors Observable, that emits as values the errors of the input Observable.
So by subscribing to this Errors Observable, we know exactly when an error occurs. Let’s now see how we could implement an immediate retry strategy using the Errors Observable.
Immediate Retry Strategy
In order to retry the failed observable immediately after the error occurs, all we have to do is return the Errors Observable without any further changes.
In this case, we are just piping the tap operator for logging purposes, so the Errors Observable remains unchanged:
Let’s remember, the Observable that we are returning from the retryWhen function call is the Notification Observable!
The value that it emits is not important, it’s only important when the value gets emitted because that is what is going to trigger a retry attempt.
Immediate Retry Console Output
If we now execute this program, we are going to find the following output in the console:
As we can see, the HTTP request failed initially, but then a retry was attempted and the second time the request went through successfully.
Let’s now have a look at the delay between the two attempts, by inspecting the network log:
As we can see, the second attempt was issued immediately after the error occurred, as expected.
Delayed Retry Strategy
Let’s now implement an alternative error recovery strategy, where we wait for example for 2 seconds after the error occurs, before retrying.
This strategy is useful for trying to recover from certain errors such as for example failed network requests caused by high server traffic.
In those cases where the error is intermittent, we can simply retry the same request after a short delay, and the request might go through the second time without any problem.
The timer Observable creation function
To implement the Delayed Retry Strategy, we will need to create a Notification Observable whose values are emitted two seconds after each error occurrence.
Let’s then try to create a Notification Observable by using the timer creation function. This timer function is going to take a couple of arguments:
- an initial delay, before which no values will be emitted
- a periodic interval, in case we want to emit new values periodically
Let’s then have a look at the marble diagram for the timer function:
As we can see, the first value 0 will be emitted only after 3 seconds, and then we have a new value each second.
Notice that the second argument is optional, meaning that if we leave it out our Observable is going to emit only one value (0) after 3 seconds and then complete.
This Observable looks like its a good start for being able to delay our retry attempts, so let’s see how we can combine it with the retryWhen and delayWhen operators.
The delayWhen Operator
One important thing to bear in mind about the retryWhen Operator, is that the function that defines the Notification Observable is only called once.
So we only get one chance to define our Notification Observable, that signals when the retry attempts should be done.
We are going to define the Notification Observable by taking the Errors Observable and applying it the delayWhen Operator.
Imagine that in this marble diagram, the source Observable a-b-c is the Errors Observable, that is emitting failed HTTP errors over time:
delayWhen Operator breakdown
Let’s follow the diagram, and learn how the delayWhen Operator works:
- each value in the input Errors Observable is going to be delayed before showing up in the output Observable
- the delay per each value can be different, and is going to be created in a completely flexible way
- in order to determine the delay, we are going to call the function passed to delayWhen (called the duration selector function) per each value of the input Errors Observable
- that function is going to emit an Observable that is going to determine when the delay of each input value has elapsed
- each of the values a-b-c has its own duration selector Observable, that will eventually emit one value (that could be anything) and then complete
- when each of these duration selector Observables emits values, then the corresponding input value a-b-c is going to show up in the output of delayWhen
- notice that the value
b
shows up in the output after the valuec
, this is normal - this is because the
b
duration selector Observable (the third horizontal line from the top) only emitted its value after the duration selector Observable ofc
, and that explains whyc
shows up in the output beforeb
Delayed Retry Strategy implementation
Let’s now put all this together and see how we can retry consecutively a failing HTTP request 2 seconds after each error occurs:
Let’s break down what is going on here:
- let’s remember that the function passed to retryWhen is only going to be called once
- we are returning in that function an Observable that will emit values whenever a retry is needed
- each time that there is an error, the delayWhen operator is going to create a duration selector Observable, by calling the timer function
- this duration selector Observable is going to emit the value 0 after 2 seconds, and then complete
- once that happens, the delayWhen Observable knows that the delay of a given input error has elapsed
- only once that delay elapses (2 seconds after the error occurred), the error shows up in the output of the notification Observable
- once a value gets emitted in the notification Observable, the retryWhen operator will then and only then execute a retry attempt
Retry Strategy Console Output
Let’s now see what this looks like in the console! Here is an example of an HTTP request that was retried 5 times, as the first 4 times were in error:
And here is the network log for the same retry sequence:
As we can see, the retries only happened 2 seconds after the error occurred, as expected!
And with this, we have completed our guided tour of some of the most commonly used RxJs error handling strategies available, let’s now wrap things up and provide some running sample code.
Running Github repository (with code samples)
In order to try these multiple error handling strategies, it’s important to have a working playground where you can try handling failing HTTP requests.
This playground contains a small running application with a backend that can be used to simulate HTTP errors either randomly or systematically. Here is what the application looks like:
Conclusions
As we have seen, understanding RxJs error handling is all about understanding the fundamentals of the Observable contract first.
We need to keep in mind that any given stream can only error out once, and that is exclusive with stream completion; only one of the two things can happen.
In order to recover from an error, the only way is to somehow generate a replacement stream as an alternative to the errored out stream, like it happens in the case of the catchError or retryWhen Operators.
I hope that you have enjoyed this post, if you would like to learn a lot more about RxJs, we recommend checking the RxJs In Practice Course course, where lots of useful patterns and operators are covered in much more detail.
Also, if you have some questions or comments please let me know in the comments below and I will get back to you.
To get notified of upcoming posts on RxJs and other Angular topics, I invite you to subscribe to our newsletter:
If you are just getting started learning Angular, have a look at the Angular for Beginners Course:
Angular CatchError is an RxJs Operator. We can use it to handle the errors thrown by the Angular Observable. Like all other RxJs operators, the CatchError
also takes an observable as input and returns an observable (or throws an error). We can use CatchError to provide a replacement observable or throw a user-defined error. Let us learn all these in this tutorial.
Catch operator was renamed as catchError in RxJs 5.5, Hence if you are using Angular 5 or prior version then use catch instead of catchError.
Table of Contents
- Handling Errors in Observable
- Using Error Callback of Subscribe method
- Catch errors in the observable stream
- Using CatchError Operator
- Syntax
- Returning a new observable
- Throws a new Error
- Retrying
- References
Handling Errors in Observable
We can handle the errors at two places.
- Using the
error
callback of thesubscribe
method - Catch errors in the observable stream
Using Error Callback of Subscribe method
We subscribe to an Observable by using the subscribe
method. The subscribe method accepts three callback methods as arguments. They are the next
value, error
, or complete
event. We use the error callback to catch & handle the errors.
For Example, consider the following code. The obs
observable multiplies the values (srcArray
) by 2 using the map operator. If the result is NaN, then we throw an error using throw new Error("Result is NaN")
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
srcArray = from([1, 2, ‘A’, 4]); obs = this.srcArray .pipe( map(val => { let result = val as number * 2; if (Number.isNaN(result)) { console.log(‘Errors Occurred in Stream’) throw new Error(«Result is NaN») } return result }), ); ngOnInit() { this.obs.subscribe( el => { console.log(‘Value Received ‘ + el) }, err => { console.log(«Error caught at Subscriber « + err) }, () => console.log(«Processing Complete.») ) } **** Output ***** Value Received 2 Value Received 4 Errors Occurred in Stream Error Caught at subscriber Error: Result is NaN |
Source Code
We subscribe and start to receive the values from the obs
observable in the ngOnInit
method. When the observable stream throws an error, it invokes the error
callback. In the error
callback, we decide what to do with the error.
Note that once the observable errors out it will not emit any values neither it calls the complete
callback. Our subscription method will never receive the final value of 8.
Catch errors in the observable stream
Another option to catch errors is to use the CatchError
Operator. The CatchError Operators catches the error in the observable stream as and when the error happens. This allows us to retry the failed observable or use a replacement observable.
To use CatchError operator, we need to import it from the rxjs/operators
as shown below
import { catchError } from ‘rxjs/operators’ |
Syntax
The catchError is a pipeable operator. We can use it in a Pipe method similar to the other operators like Map, etc.
The catchError operator gets two argument.
The first argument is err
, which is the error object that was caught.
The second argument is caught
, which is the source observable. We can return it back effectively retrying the observable.
The catchError must return a new observable or it can throw an error.
Returning a new observable
The following examples shows the use of catchError
operator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
srcArray = from([1, 2, ‘A’, 4]); obs = this.srcArray .pipe( map(val => { let result = val as number * 2; if (Number.isNaN(result)) { console.log(‘Errors Occurred in Stream’) throw new Error(«Result is NaN») } return result }), catchError(error => { console.log(‘Caught in CatchError. Returning 0’) return of(0); //return from([‘A’,’B’,’C’]) }) ); //Output Value Received 2 Value Received 4 Errors Occurred in Stream Caught in CatchError. Returning 0 Value Received 0 Observable Completed |
Source Code
In the code above, the map emits the values 2 & 4, which is input to the catchError
. Since there are no errors, catchError
forwards it to the output. Hence the subscribers receive values 2 & 4.
The catchError
comes into play, when the map operator throws an error. The catchError
handle the error and must return a new observable (or throw an error). In the example above we return a new observable i.e. of(0)
. You can also emit any observable for example return from(['A','B','C'])
etc
You can also return the original observable. Just use the return this.obs;
instead of return of(0);
. But beware, It will result in an infinite loop.
The new observable is automatically subscribed and the subscriber gets the value 0
. The new observable now finishes and emits the complete
event.
Since the original observable ended in a error, it will never emit the the value 8.
Throws a new Error
catchError
can also throw an error. In the following example, we use the throw new Error(error)
to throw a JavaScript error. This error will propagate to the subscriber as shown in the example below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
obs = this.srcArray .pipe( map(val => { let result = val as number * 2; if (Number.isNaN(result)) { console.log(‘Errors Occurred in Stream’) throw new Error(«Result is NaN») } return result }), catchError(error => { console.log(‘Caught in CatchError. Throwing error’) throw new Error(error) }) ); //OUTPUT Value Received 2 Value Received 4 Errors Occurred in Stream Caught in CatchError. Throwing error Error caught at Subscriber Error: Error: Result is NaN |
Source Code
We can also make use of throwError
to return an observable. Remember that the throwError does not throw an error like throw new Error
but returns an observable, which emits an error immediately.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
obs = this.srcArray .pipe( map(val => { let result = val as number * 2; if (Number.isNaN(result)) { console.log(‘Errors Occurred in Stream’) throw new Error(«Result is NaN») } return result }), catchError(error => { console.log(‘Caught in CatchError. Throwing error’) return throwError(error); }) ); //OUTPUT Value Received 2 Value Received 4 Errors Occurred in Stream Caught in CatchError. Throwing error Error caught at Subscriber Error: Result is NaN |
Source code
Retrying
You can also retry the observable using the Retry operator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
obs = this.srcArray .pipe( map(val => { let result = val as number * 2; if (Number.isNaN(result)) { console.log(‘Errors Occurred in Stream’) throw new Error(«Result is NaN») } return result }), retry(2), catchError((error,src) => { console.log(‘Caught in CatchError. Throwing error’) throw new Error(error) }) ); //Output Value Received 2 Value Received 4 Errors Occurred in Stream Value Received 2 Value Received 4 Errors Occurred in Stream Value Received 2 Value Received 4 Errors Occurred in Stream Caught in CatchError. Throwing error Error caught at Subscriber Error: Error: Result is NaN |
Source Code
The catchError
gets the source observable as the second argument. If we return it, it will get subscribed again effectively retrying the observable.
Ensure that you keep track of no of tries so that you can stop the observable after a few failed attempts. Otherwise, you may run into an infinite loop if the observable always emits an error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
obs = this.srcArray .pipe( map(val => { let result = val as number * 2; if (Number.isNaN(result)) { console.log(‘Errors Occurred in Stream’) throw new Error(«Result is NaN») } return result }), catchError((error,src) => { console.log(‘Caught in CatchError. Throwing error’) this.count++; if (this.count < 2) { return src; } else { throw new Error(error) } }) ); |
Source Code
References
- catchError
- throwError
- retry API
catch / catchError
signature: catchError(project : function): Observable
Gracefully handle errors in an observable sequence.
⚠ Remember to return an observable from the catchError function!
Examples
(
example tests
)
Example 1: Catching error from observable
(
StackBlitz
| jsBin |
jsFiddle )
// RxJS v6+ import { throwError, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; //emit error const source = throwError('This is an error!'); //gracefully handle error, returning observable with error message const example = source.pipe(catchError(val => of(`I caught: ${val}`))); //output: 'I caught: This is an error' const subscribe = example.subscribe(val => console.log(val));
Example 2: Catching rejected promise
(
StackBlitz
| jsBin |
jsFiddle )
// RxJS v6+ import { timer, from, of } from 'rxjs'; import { mergeMap, catchError } from 'rxjs/operators'; //create promise that immediately rejects const myBadPromise = () => new Promise((resolve, reject) => reject('Rejected!')); //emit single value after 1 second const source = timer(1000); //catch rejected promise, returning observable containing error message const example = source.pipe( mergeMap(_ => from(myBadPromise()).pipe(catchError(error => of(`Bad Promise: ${error}`))) ) ); //output: 'Bad Promise: Rejected' const subscribe = example.subscribe(val => console.log(val));
Example 3: Catching errors comparison when using switchMap/mergeMap/concatMap/exhaustMap
(
StackBlitz
)
// switchMap in example below can be replaced with mergeMap/concatMap/exhaustMap, the same behaviour applies import { throwError, fromEvent, of } from 'rxjs'; import { catchError, tap, switchMap, mergeMap, concatMap, exhaustMap } from 'rxjs/operators'; const fakeRequest$ = of().pipe( tap(_ => console.log('fakeRequest')), throwError ); const iWillContinueListening$ = fromEvent( document.getElementById('continued'), 'click' ).pipe( switchMap(_ => fakeRequest$.pipe(catchError(_ => of('keep on clicking!!!')))) ); const iWillStopListening$ = fromEvent( document.getElementById('stopped'), 'click' ).pipe( switchMap(_ => fakeRequest$), catchError(_ => of('no more requests!!!')) ); iWillContinueListening$.subscribe(console.log); iWillStopListening$.subscribe(console.log);
Additional Resources
- catchError 📰 — Official docs
- catchError — In Depth Dev Reference
- Error handling operator: catch
🎥 💵 — André Staltz
📁 Source Code:
https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/catchError.ts
- Operators
- Error Handling
- Catch
recover from an onError notification by continuing the sequence without error
The Catch operator intercepts an
There are several variants of the Catch operator, and a
In some ReactiveX implementations, there is an operator called something likeonError
notification from the source Observable and, instead of passing it through to any
observers, replaces it with some other item or sequence of items, potentially allowing
the resulting Observable to terminate normally or not to terminate at all.
variety of names used by different ReactiveX implementations to describe this operation,
as you can see in the sections below.
“OnErrorResumeNext” that behaves like a Catch
variant: specifically reacting to an onError
notification from the source
Observable. In others, there is an operator with that name that behaves more like a
Concat variant: performing the concatenation operation
regardless of whether the source Observable terminates normally or with an error. This is
unfortunate and confusing, but something we have to live with.
See Also
- Concat
- Retry
- Introduction to Rx: Catch
Language-Specific Information:
RxClojure catch*
RxClojure implements this operator as
You may replace the first function parameter (the predicate that evaluates the exception)
is equivalent to:
catch*
. This operator takes two arguments,
both of which are functions of your choosing that take the exception raised by
onError
as their single parameters. The first function is a predicate. If it
returns false
, catch*
passes the onError
notification
unchanged to its observers. If it returns true
, however, catch*
swallows the error, calls the second function (which returns an Observable), and passes along
the emissions and notifications from this new Observable to its observers.
with a class object representing a variety of exception. If you do this, catch*
will treat it as equivalent to predicate that performs an instance?
check to see
if the exception from the onError
notification is an instance of the class
object. In other words:
Sample Code
(->> my-observable
(catch* IllegalArgumentException
(fn [e] (rx/return 1)))
)
(->> my-observable
(catch* (fn [e] (-> instance? IllegalArgumentException e))
(fn [e] (rx/return 1)))
)
RxCpp
RxCpp does not implement the Catch operator.
RxGroovy onErrorResumeNext onErrorReturn onExceptionResumeNext
RxGroovy implements the Catch operator in the same way as
does RxJava. There are three distinct operators that provide this functionality:
onErrorReturn
- instructs an Observable to emit a particular item when it encounters an error, and then terminate normally
onErrorResumeNext
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error
onExceptionResumeNext
- instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable)
onErrorReturn
The onErrorReturn
method returns an Observable that mirrors the behavior of the
source Observable, unless that Observable invokes onError
in which case, rather
than propagating that error to the observer, onErrorReturn
will instead emit a
specified item and invoke the observer’s onCompleted
method, as shown in
the following sample code:
Sample Code
def myObservable = Observable.create({ aSubscriber ->
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('Four');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('Three');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('Two');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('One');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onError();
});
myObservable.onErrorReturn({ return('Blastoff!'); }).subscribe(
{ println(it); }, // onNext
{ println("Error: " + it.getMessage()); }, // onError
{ println("Sequence complete"); } // onCompleted
);
Four
Three
Two
One
Blastoff!
Sequence complete
onErrorReturn(Func1)
onErrorResumeNext
The onErrorResumeNext
method returns an Observable that mirrors the behavior of
the source Observable, unless that Observable invokes onError
in which case,
rather than propagating that error to the observer, onErrorResumeNext
will
instead begin mirroring a second, backup Observable, as shown in the following sample code:
Sample Code
def myObservable = Observable.create({ aSubscriber ->
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('Three');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('Two');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('One');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onError();
});
def myFallback = Observable.create({ aSubscriber ->
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('0');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('1');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onNext('2');
if(false == aSubscriber.isUnsubscribed()) aSubscriber.onCompleted();
});
myObservable.onErrorResumeNext(myFallback).subscribe(
{ println(it); }, // onNext
{ println("Error: " + it.getMessage()); }, // onError
{ println("Sequence complete"); } // onCompleted
);
Three
Two
One
0
1
2
Sequence complete
onErrorResumeNext(Func1)
onErrorResumeNext(Observable)
onExceptionResumeNext
Much like onErrorResumeNext
method, this returns an Observable that mirrors the
behavior of the source Observable, unless that Observable invokes onError
in
which case, if the Throwable passed to onError
is an Exception, rather than
propagating that Exception to the observer, onExceptionResumeNext
will instead
begin mirroring a second, backup Observable. If the Throwable is not an Exception, the
Observable returned by onExceptionResumeNext
will propagate it to its
observer’s onError
method and will not invoke its backup Observable.
onExceptionResumeNext(Observable)
RxJava 1․x onErrorResumeNext onErrorReturn onExceptionResumeNext
RxJava implements the Catch operator with three distinct
operators:
onErrorReturn
- instructs an Observable to emit a particular item when it encounters an error, and then terminate normally
onErrorResumeNext
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error
onExceptionResumeNext
- instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable)
onErrorReturn
The onErrorReturn
method returns an Observable that mirrors the behavior of the
source Observable, unless that Observable invokes onError
in which case, rather
than propagating that error to the observer, onErrorReturn
will instead emit a
specified item and invoke the observer’s onCompleted
method.
onErrorReturn(Func1)
onErrorResumeNext
The onErrorResumeNext
method returns an Observable that mirrors the behavior of
the source Observable, unless that Observable invokes onError
in which case,
rather than propagating that error to the observer, onErrorResumeNext
will
instead begin mirroring a second, backup Observable.
onErrorResumeNext(Func1)
onErrorResumeNext(Observable)
onExceptionResumeNext
Much like onErrorResumeNext
method, this returns an Observable that mirrors the
behavior of the source Observable, unless that Observable invokes onError
in
which case, if the Throwable passed to onError
is an Exception, rather than
propagating that Exception to the observer, onExceptionResumeNext
will instead
begin mirroring a second, backup Observable. If the Throwable is not an Exception, the
Observable returned by onExceptionResumeNext
will propagate it to its
observer’s onError
method and will not invoke its backup Observable.
onExceptionResumeNext(Observable)
RxJS catch onErrorResumeNext
RxJS implements the Catch operator with two distinct
operators:
catch
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error
onErrorResumeNext
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error or if the source Observable terminates normally
catch
catch
is found in the following distributions:
rx.js
rx.all.js
rx.all.compat.js
rx.compat.js
rx.lite.js
rx.lite.compat.js
onErrorResumeNext
This implementation borrows the confusing nomenclature from Rx.NET, in which
onErrorResumeNext
switches to a back-up Observable both on an error and
on a normal, error-free termination of the source Observable.
onErrorResumeNext
is found in the following distributions:
rx.js
rx.compat.js
RxKotlin onErrorResumeNext onErrorReturn onExceptionResumeNext
RxKotlin implements the Catch operator in the same way as
does RxJava. There are three distinct operators that provide this functionality:
onErrorReturn
- instructs an Observable to emit a particular item when it encounters an error, and then terminate normally
onErrorResumeNext
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error
onExceptionResumeNext
- instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable)
onErrorReturn
The onErrorReturn
method returns an Observable that mirrors the behavior of the
source Observable, unless that Observable invokes onError
in which case, rather
than propagating that error to the observer, onErrorReturn
will instead emit a
specified item and invoke the observer’s onCompleted
method.
onErrorResumeNext
The onErrorResumeNext
method returns an Observable that mirrors the behavior of
the source Observable, unless that Observable invokes onError
in which case,
rather than propagating that error to the observer, onErrorResumeNext
will
instead begin mirroring a second, backup Observable.
onExceptionResumeNext
Much like onErrorResumeNext
method, this returns an Observable that mirrors the
behavior of the source Observable, unless that Observable invokes onError
in
which case, if the Throwable passed to onError
is an Exception, rather than
propagating that Exception to the observer, onExceptionResumeNext
will instead
begin mirroring a second, backup Observable. If the Throwable is not an Exception, the
Observable returned by onExceptionResumeNext
will propagate it to its
observer’s onError
method and will not invoke its backup Observable.
RxNET Catch OnErrorResumeNext
Rx.NET implements the Catch operator with two distinct
operators:
Catch
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error
OnErrorResumeNext
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error or if the source Observable terminates normally
Catch
The Catch
operator has a variant that allows you to specify which sort of
Exception you want to catch. If you use that variant of the operator, any other Exceptions
will be passed through to the observer as if the Catch
operator had not been
applied.
OnErrorResumeNext
This implementation introduces a confusing nomenclature, in which in spite of its name
OnErrorResumeNext
switches to a back-up Observable both on an error and
on a normal, error-free termination of the source Observable. It is therefore more like a
concatenation operator.
RxPHP catch
RxPHP implements this operator as catch
.
Continues an observable sequence that is terminated by an exception with the next observable sequence.
Sample Code
//from https://github.com/ReactiveX/RxPHP/blob/master/demo/catch/catch.php $obs2 = RxObservable::of(42); $source = RxObservable::error(new Exception('Some error')) ->catch(function (Throwable $e, RxObservable $sourceObs) use ($obs2) { return $obs2; }); $subscription = $source->subscribe($stdoutObserver);
Next value: 42 Complete!
RxPY catch_exception on_error_resume_next
RxPY implements the Catch operator with two distinct
operators:
catch_exception
- instructs an Observable, if it encounters an error, to begin emitting items from a set of other Observables, one Observable at a time, until one of those Observables terminates successfully
on_error_resume_next
- instructs an Observable to concatenate items emitted by a set of other Observables, one Observable at a time, regardless of whether the source Observable or any subsequent Observable terminates with an error
catch_exception
You may pass catch_exception
a set of back-up Observables either as individual
function parameters or as a single array of Observables. If it encounters an
onError
notification from the source Observable, it will subscribe to and begin
mirroring the first of these back-up Observables. If this back-up Observable itself
issues an onError
notification, catch_exception
will swallow it
and switch over to the next back-up Observable. If any of these Observables issues an
onCompleted
notification, catch_exception
will pass this along
and will stop.
on_error_resume_next
You may pass on_error_resume_next
a set of back-up Observables either as
individual function parameters, as a single array of Observables, or as a factory function
that generates Observables. When the source Observable terminates, whether normally or with
an error, on_error_resume_next
will subscribe to and begin mirroring the first
of these back-up Observables, and then will recursively continue this concatenation process
for each additional Observable until there are no more Observables to mirror, at which time
it will pass on the onError
or onCompleted
notification from the
last of these Observables.
Rxrb on_error_resume_next rescue_error
Rx.rb implements the Catch operator with two distinct
operators:
rescue_error
- instructs an Observable to begin emitting items from another Observable, or from an Observable returned from an action, if it encounters an error
on_error_resume_next
- instructs an Observable to concatenate items emitted by another Observable to the sequence emitted by the source Observable, regardless of whether the source Observable terminates normally or with an error
rescue_error
You may pass rescue_error
either an Observable or a factory action that
generates an Observable.
on_error_resume_next
In Rx.rb, on_error_resume_next
inherits the misleading nomenclature from Rx.NET
in that it concatenates the second Observable sequence to the source sequence whether that
source sequence terminates normally or with an error.
RxScala onErrorFlatMap onErrorResumeNext onErrorReturn onExceptionResumeNext
Rx.rb implements the Catch operator with four distinct
operators:
onErrorFlatMap
- replaces all
onError
notifications from a misbehaving Observable into the emissions from a secondary Observable onErrorResumeNext
- instructs an Observable to begin emitting a second Observable sequence if it encounters an error
onErrorReturn
- instructs an Observable to emit a particular item when it encounters an error, and then terminate normally
onExceptionResumeNext
- instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable)
onErrorFlatMap
Because
Note that you should apply
Note that onErrorFlatMap
handles a special case: a source Observable that is noncompliant
with the Observable contract in such a way that it may interleave
onError
notifications with its emissions without terminating. This operator allows you to
replace those onError
notifications with the emissions of an Observable of your choosing
without unsubscribing from the source, so that any future items emitted from the source will
be passed along to observers as though the sequence had not been interrupted with an
onError
notification.
onErrorFlatMap
is designed to work with pathological source Observables
that do not terminate after issuing an error, it is mostly useful in debugging/testing
scenarios.
onErrorFlatMap
directly to the pathological source
Observable, and not to that Observable after it has been modified by additional operators,
as such operators may effectively renormalize the source Observable by unsubscribing from it
immediately after it issues an error. Above, for example, is an illustration showing how
onErrorFlatMap
will respond to two error-generating Observables that have been
merged by the Merge operator:
onErrorFlatMap
will not react to both errors generated by both
Observables, but only to the single error passed along by merge
.
onErrorResumeNext
The onErrorResumeNext
method returns an Observable that mirrors the behavior of
the source Observable, unless that Observable invokes onError
in which case,
rather than propagating that error to the observer, onErrorResumeNext
will
instead begin mirroring a second, backup Observable.
onErrorReturn
The onErrorReturn
method returns an Observable that mirrors the behavior of the
source Observable, unless that Observable invokes onError
in which case, rather
than propagating that error to the observer, onErrorReturn
will instead emit a
specified item and invoke the observer’s onCompleted
method.
onExceptionResumeNext
Much like onErrorResumeNext
method, this returns an Observable that mirrors the
behavior of the source Observable, unless that Observable invokes onError
in
which case, if the Throwable passed to onError
is an Exception, rather than
propagating that Exception to the observer, onExceptionResumeNext
will instead
begin mirroring a second, backup Observable. If the Throwable is not an Exception, the
Observable returned by onExceptionResumeNext
will propagate it to its
observer’s onError
method and will not invoke its backup Observable.
November 26, 2018
This page will walk through Angular RxJS catchError
example. RxJS catchError
operator catches the error thrown by Observable
and handles it by returning a new Observable
or throwing user defined error. catchError
is the changed name of catch
starting from RxJS 5.5. Angular 6 integrates RxJS 6 and hence Angular 6 onwards we need to use RxJS catchError
operator to handle error. catchError
is the pipeable operator and it is used within pipe
function of Observable
. The parameter of catchError
is a function that takes error as argument and returns Observable
instance. catchError
is imported as following.
import { catchError } from 'rxjs/operators';
Here we will provide catchError
examples. We will understand the difference between catchError
and subscribe
error callback and we will also provide catchError
examples with throwError
and retry
operators.
Contents
- Technologies Used
- Observable and catchError
- catchError and throwError
- retry and catchError
- Complete Example
- Run Application
- References
- Download Source Code
Technologies Used
Find the technologies being used in our example.
1. Angular 7.0.0
2. Angular CLI 7.0.3
3. TypeScript 3.1.1
4. Node.js 10.3.0
5. NPM 6.1.0
6. RxJS 6.3.3
7. In-Memory Web API 0.6.1
Observable and catchError
Observable
works asynchronously and once there is an error in Observable
for any element then it stops emitting other values. To handle Observable
error, we can use catchError
operator. We use catchError
for logging a proper message and to throw user defined error or instance of Observable
with default data. Using catchError
does not mean that Observable
will emit other values which have not been emitted because of error. Let us understand by example.
Look into the code.
of("A", "B", "C", "D", "E").pipe( map(el => { if (el === "C") { throw new Error("Error occurred."); } return el; }) ).subscribe(el => console.log(el), err => console.error(err), () => console.log("Processing Complete.") );
Find the output.
A B Error: "Error occurred."
In the above code for the element «C», it throws error. The error is caught by subscribe
. In the subscribe
, first callback is to get result, second callback is to log error if any, third callback is to log completion. Once source Observable
throws error, it stops emitting rest of the values it has.
In subscribe
if second callback for error executes then the third callback for completion does not execute and if third callback for completion executes, it means there is no error or error is handled by catchError
in source Observable
. If we throw error from catchError
then in subscribe
, second callback for error will execute and third callback for completion will not execute.
If we want to handle error before subscribe
then we need to use catchError
that handles the error in source Observable
and returns new instance of Observable
that can be subscribed.
of("A", "B", "C", "D", "E").pipe( map(el => { if (el === "C") { throw new Error("Error occurred."); } return el; }), catchError(err => { console.error(err.message); console.log("Error is handled"); return of("Z"); }) ).subscribe(el => console.log(el), err => console.error(err), () => console.log("Processing Complete.") );
Find the output.
A B Error occurred. Error is handled Z Processing Complete.
catchError and throwError
After caching error either we can return Observable
instance with default data or can throw error using RxJS throwError
. throwError
emits error notification immediately. Find the code snippet using catchError
and throwError
.
of("A", "B", "C", "D", "E").pipe( map(el => { if (el === "C") { throw new Error("Error occurred."); } return el; }), catchError(err => { console.error(err.message); console.log("Error is handled"); return throwError("Error thrown from catchError"); }) ).subscribe(el => console.log(el), err => console.error(err), () => console.log("Processing Complete.") );
Find the output.
A B Error occurred. Error is handled Error thrown from catchError
From the catchError
block we have returned error using throwError
. The error message is shown by error callback of subscribe
.
retry and catchError
RxJS provides retry
operator that resubscribes the Observable
for the given number of count when there is an error. Before throwing error Observable
is resubscribed for the given number of count by retry
operator and if still there is an error, then error is thrown. retry
is useful to hit the URL many times. It is possible that because of network bandwidth, URL does not return successful data in one time and when it reties, it may return data successfully. If after retying still there is error in Observable
then catchError
can be used to return Observable
with user defined default data.
Find the sample code to use retry
with catchError
.
getBook(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id).pipe( retry(3), catchError(err => { console.log(err); return of(null); }) ); }
Complete Example
book.component.ts
import { Component, OnInit } from '@angular/core'; import { Observable, of, pipe, throwError } from 'rxjs'; import { map, switchMap, debounceTime, catchError } from 'rxjs/operators'; import { BookService } from './book.service'; import { Book } from './book'; import { FormControl, FormBuilder, FormGroup } from '@angular/forms'; @Component({ selector: 'app-book', template: ` <h3>Search Book</h3> <form [formGroup]="bookForm"> ID: <input formControlName="bookId"> </form> <br/> <div *ngIf="book"> Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </div> ` }) export class BookComponent implements OnInit { book: Book; constructor(private bookService: BookService, private formBuilder: FormBuilder) { } ngOnInit() { this.searchBook(); of("A", "B", "C", "D", "E").pipe( map(el => { if (el === "C") { throw new Error("Error occurred."); } return el; }), catchError(err => { console.error(err.message); console.log("Error is handled"); return throwError("Error thrown from catchError"); }) ).subscribe(el => console.log(el), err => console.error(err), () => console.log("Processing Complete.") ); } bookId = new FormControl(); bookForm: FormGroup = this.formBuilder.group({ bookId: this.bookId } ); searchBook() { this.bookId.valueChanges.pipe( debounceTime(500), switchMap(id => { console.log(id); return this.bookService.getBook(id); }) ).subscribe(res => this.book = res); } }
book.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { Book } from './book'; import { catchError, retry } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class BookService { bookUrl = "/api/books"; constructor(private http: HttpClient) { } getBook(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id).pipe( retry(3), catchError(err => { console.log(err); return of(null); }) ); } }
book.ts
export interface Book { id: number; name: string; category: string; }
app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-book></app-book> ` }) export class AppComponent { }
test-data.ts
import { InMemoryDbService } from 'angular-in-memory-web-api'; export class TestData implements InMemoryDbService { createDb() { let bookDetails = [ { id: 101, name: 'Angular by Krishna', category: 'Angular' }, { id: 102, name: 'Core Java by Vishnu', category: 'Java' }, { id: 103, name: 'NgRx by Rama', category: 'Angular' } ]; return { books: bookDetails }; } }
app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { BookComponent } from './book.component'; //For InMemory testing import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { TestData } from './test-data'; @NgModule({ imports: [ BrowserModule, HttpClientModule, FormsModule, ReactiveFormsModule, InMemoryWebApiModule.forRoot(TestData) ], declarations: [ AppComponent, BookComponent ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { }
Run Application
To run the application, find the steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Install angular-in-memory-web-api@0.6.1
4. Run ng serve using command prompt.
5. Access the URL http://localhost:4200
Find the print screen of the output.
References
RxJS catchError
Angular: The RxJS library
Pipeable Operators
Download Source Code
The two key concepts you would want to know to handle errors in Observables are: catch
and retry
. As the name suggests catch
allows us to catch the errors and retry
will enable us to retry the action in case of an error.
Catch
Catch does not prevent the error from happening. It merely allows us to catch the error and do something with that error. Generally, we can wrap that error in an Observable so that the Observable chain can continue. We could also display that error to the end-user in the catch block while we continue the Observable chain.
Let us go through some examples to understand this better. I will take the example of concatenating two intervals from my previous blog post. This time I will add a third Observable that produces an error.
import Rx from 'rxjs';
const interval1 = Rx.Observable.interval(1000).map(i => `first: ${i}`).take(5);
const errorObservable = Rx.Observable.throw(new Error("An error occurred, cannot proceed"));
const interval2 = Rx.Observable.interval(500).map(i => `second: ${i}`).take(5);
const combinedInterval = Rx.Observable.concat(interval1, errorObservable, interval2);
combinedInterval.subscribe(
data => console.log(`${data}`)
);
The output will be:
first: 0
first: 1
first: 2
first: 3
first: 4
Error: An error occurred, cannot proceed
The output is as expected. We got an error after the first Observable, so the second one never ran. The problem is, what if we still wanted to continue the concat
chain despite the error? In this situation is where catch
comes in. We will modify the example to use catch
and display a message.
import Rx from 'rxjs';
const interval1 = Rx.Observable.interval(1000).map(i => `first: ${i}`).take(5);
const errorObservable = Rx.Observable.throw(new Error("An error occurred, cannot proceed")).catch(e => Rx.Observable.of(e.message));
const interval2 = Rx.Observable.interval(500).map(i => `second: ${i}`).take(5);
const combinedInterval = Rx.Observable.concat(interval1, errorObservable, interval2);
combinedInterval.subscribe(
data => console.log(`${data}`)
);
Since now we have a catch
statement for any error in the Observable, it returns a regular Observable with an error message. The output will be:
first: 0
first: 1
first: 2
first: 3
first: 4
An error occurred, cannot proceed
second: 0
second: 1
second: 2
second: 3
second: 4
We see the execution of all Observables despite the error in one of them.
P.S. The concat
example might not be the best example to demonstrate catch
. Don’t lose hope! I use catch
and retry
both to explain retry
in the next section.
Retry
retry
method retries the Observable that generated the error. retry
is helpful in case you are making an API call and you would like to keep retrying until you get success. Key things to remember about retry
.
- By default, it will retry infinite times.
- It does take a numerical argument if you would like to limit the number of retries.
- Do not use retry if you are converting a promise into an Observable (explained below).
-
retry
truly unsubscribes from an error generating Observable and subscribes again. - A retry needs to execute the generator function of the Observable again. So, retry is only useful in case of cold Observables.
- Hot Observable retry will not invoke the generator again, so it is useless.
When we resubscribe to a fromPromise
, it still caches the resolve/reject status of the promise. It does not invoke the complete action again. This is why retry
does not work with Observables made from promises.
To showcase an example of retry
, I will create a function called dummyApi
. The function will mimic an API call to the backend and return an error Observable. We will try and catch
the error as well as retry
the call.
Without catch or retry
import Rx from 'rxjs';
const dummyApi = () => {
return new Rx.Observable(observer => {
setTimeout(() => {
observer.error(new Error("API call failed. Sorry!")); // API call responds with an error
}, 1000); // API call takes 1 second to respond
});
}
dummyApi()
.do(() => console.log("Executing next Observable, chain continues"))
.subscribe(
data => console.log(data),
error => console.log(error.message) // We handle error here by displaying the message
)
The output will be:
We logged the error message, but the problem is do
operator in the chain never got executed. Now we use the catch
operator (and this is a better example as I promise 😉).
Without retry (with catch)
import Rx from 'rxjs';
const dummyApi = () => {
return new Rx.Observable(observer => {
setTimeout(() => {
observer.error(new Error("API call failed. Sorry!"))
}, 1000);
});
}
dummyApi()
.catch(err => Rx.Observable.of(err.message)) // Wrap the error in a regular Observable so chain continues
.do(() => console.log("Executing next Observable, chain continues")) // `do` operator logs the message
.subscribe(
data => console.log(data) // The error wrapped in a regular observable could not be logged
)
The output will be:
Executing next Observable, chain continues
API call failed. Sorry!
Much better but we are still not retrying!
With retry and catch!
import Rx from 'rxjs';
const dummyApi = () => {
return new Rx.Observable(observer => {
console.log("Calling API"); // Added the log to display retry
setTimeout(() => {
observer.error(new Error("API call failed. Sorry!"))
}, 1);
});
}
dummyApi()
.retry(3) // Retry 3 times
.catch(err => Rx.Observable.of(err.message))
.do(() => console.log("Executing next Observable, chain continues"))
.subscribe(
data => console.log(data)
)
I added a console log statement to dummyApi
so we can see retry attempts. The output will be:
Calling API
Calling API
Calling API
Calling API
Executing next Observable, chain continues
API call failed. Sorry!
The API gets called, it fails, and then it is retried three more times. That is why we see four logs with «Calling API» (original call plus three retries).
The above code handles retries, logs the error message if any, and continues the chain of Observable operators. Voila!
Happy coding 👋🏼