We’ve almost run out of operators for all the basic transformations, filters, and combinations. Error Handling is one of the most difficult aspects of asynchronous behavior, especially when there are multiple overlapping asynchronous behaviors.
Let’s take a look at how errors can be handled in RxJS.
Operators
catch
In RxJS, a catch can be used directly to handle errors. In RxJS, a catch can return an Observable that returns a new value. Let’s look at an example:
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.catch(error= > Rx.Observable.of('h'));
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
JSBin | JSFiddle
In this example we send a String every 500 milliseconds and use the String method toUpperCase() toUpperCase the English letter of the String, During the process, a Number (Number 2) is sent out for some unknown reason, resulting in an exception (there is no toUpperCase method for the value). In this case, we can catch the error by following the catch.
A catch can return a new Observable, Promise, Array, or any Iterable event to pass along subsequent elements.
In our example, it will end when X is sent. The Marble Diagram is shown below
source: --a----b----c----d----2|
map(x => x.toUpperCase())
----a----b----c----d----X|
catch(error => Rx.Observable.of('h'))
example: ----a----b----c----d----h|Copy the code
As you can see here, when an error occurs it goes into a catch and reprocesses a new Observable, which we can use to send the desired value.
You can also end observable after an error, as follows
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.catch(error= > Rx.Observable.empty());
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
JSBin | JSFiddle
Return an Empty Observable to complete it.
In addition, the catch callback can receive a second parameter, which receives the current Observalbe. We can re-execute by sending back the current Observable, as shown in the following example
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.catch((error, obs) = > obs);
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
JSBin | JSFiddle
As you can see here, we simply echo the current obserable to re-execute it, as shown in the Marble Diagram below
source: --a----b----c----d----2|
map(x => x.toUpperCase())
----a----b----c----d----X|
catch((error, obs) => obs)
example: ----a----b----c----d--------a----b----c----d-..Copy the code
Because we are simply demonstrating, this loop will continue indefinitely, which is usually used in practice when the line is disconnected and reconnected.
A simplified version of the above approach is called retry().
retry
If we want an Observable to fail, we can retry it, as we did in the previous example
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.retry();
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
JSBin | JSFiddle
Often this infinite retry is placed in an instant synchronous reconnection, allowing us to try again and again after the connection is disconnected. Alternatively, we can set it to try only a few times, as follows
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.retry(1);
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});// a
// b
// c
// d
// a
// b
// c
// d
// Error: TypeError: x.toUpperCase is not a functionCopy the code
JSBin | JSFiddle
Here we pass a retry value of 1, which allows us to send an error after only one retry, as shown in the Marble Diagram
source: --a----b----c----d----2|
map(x => x.toUpperCase())
----a----b----c----d----X|
retry(1)
example: ----a----b----c----d--------a----b----c----d----X|Copy the code
This approach is useful in the case of an HTTP request failure, where you can resend the request several times before displaying an error message.
retryWhen
RxJS also provides another method, retryWhen, which puts the exception elements into an Observable so that we can operate on the observable directly and re-subscribe to the original Observable after the operation is complete.
Let’s go straight to the code here
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.retryWhen(errorObs= > errorObs.delay(1000));
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
JSBin | JSFiddle
RetryWhen we pass in a callback that takes a parameter to an Observable, This Observable is not the original Observable (Example), but an Observable composed of errors sent by exception events. We can operate this observable composed of errors. After this processing is complete, we re-subscribe to our original Observable.
In this example, we delay the error observable by 1 second, which will delay the execution of subsequent re-subscribed actions by 1 second. The Marble Diagram is shown below
source: --a----b----c----d----2|
map(x => x.toUpperCase())
----a----b----c----d----X|
retryWhen(errorObs => errorObs.delay(1000))
example: ----a----b----c----d-------------------a----b----c----d-...Copy the code
As you can see from the figure above, subsequent resubscriptions are delayed, but in practice we do not use retryWhen to delay resubscriptions, and usually use catch directly to do so. This is just to demonstrate retryWhen’s behavior. In practice, we usually use retryWhen for error notification or exception collection, as shown below
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source
.map(x= > x.toUpperCase())
.retryWhen(
errorObs= > errorObs.map(err= > fetch('... ')));
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
Errorobs. map(err => fetch(‘… ‘) we can turn every error in errorObs into an API send, usually an API like a message to a company’s messaging channel (Slack etc.), which lets engineers know immediately which API might be down so we can deal with it in real time.
RetryWhen actually creates a Subject in the back and puts errors in it, and it’s going to subscribe internally to that Subject, because we haven’t gotten to the idea of Subject yet, you can just think of it as an Observable, Also remember that the observalbe default is infinite, and if we end it, the observalbe will end as well.
repeat
We might sometimes want to retry the effect of repeating a subscription over and over again, but no errors occur, so we can use repeat to do this, as shown in the following example
var source = Rx.Observable.from(['a'.'b'.'c'])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source.repeat(1);
example.subscribe({
next: (value) = > { console.log(value); },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});// a
// b
// c
// a
// b
// c
// completeCopy the code
JSBin | JSFiddle
Repeat behavior is basically the same as retry, but retry is triggered only when an exception occurs, as shown in the Marble Diagram below
source: --a----b----c|
repeat(1)
example: ----a----b----c----a----b----c|Copy the code
Similarly, we can let the argument loop indefinitely without giving it a loop, as follows
var source = Rx.Observable.from(['a'.'b'.'c'])
.zip(Rx.Observable.interval(500), (x,y) => x);
var example = source.repeat();
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }});Copy the code
JSBin | JSFiddle
This allows us to do repetitive behavior, which can be used to set up a poll, where we continually send requests to update the screen.
Finally, let’s look at a small example of error handling in action
const title = document.getElementById('title');
var source = Rx.Observable.from(['a'.'b'.'c'.'d'.2])
.zip(Rx.Observable.interval(500), (x,y) => x)
.map(x= > x.toUpperCase());
// Usually the source will set up a real-time synchronous connection, such as a Web socket
var example = source.catch(
(error, obs) = > Rx.Observable.empty()
.startWith('Connection error: reconnection after 5 seconds')
.concat(obs.delay(5000))); example.subscribe({next: (value) = > { title.innerText = value },
error: (err) = > { console.log('Error: ' + err); },
complete: (a)= > { console.log('complete'); }});Copy the code
JSBin | JSFiddle
In fact, this example is an imitation of a catch that returns a new Observable when an instant synchronous disconnection occurs. The Observable sends an error message and delays merging the original Observable for 5 seconds. But it clearly shows how flexible RxJS can be when it comes to error handling.
Today’s summary
Today we talked about three error handling methods and a repeat operator, these methods are very likely to be used in practice, I wonder if you have learned today? If you have any questions, feel free to leave a comment below. Thank you!