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!