In Rx you'll frequently find yourself needing to handle errors. In this contrived example I have an especially ornery map function here that really doesn't like the number 3. If I was to run this code right now, you'll see it will error out when it gets to 3, and say how much it hates threes. What if you want to handle this error? To handle the error you can use the catch operator. The catch operator gives you the error and expects you to return an observable if you want to successfully continue.
You can also return an observable that throws if you want to go down the error path. But you'll notice that it only got to, when we did this successfully, you'll notice that it only gets to go ahead and then complete. It doesn't actually hit my number 4 value. That's because once this observable has errored, the error returned by map, it's essentially unsubscribed and cannot continue on to the 4.
So one interesting thing here about how we're using catch, catch when used this way, when you're not examining the error that it's giving you, is essentially as the same thing as another operator, which is onErrorResumeNext. If you run this, you should get exactly the same results, so you can see a difference, there. Generally the only reason I would use onErrorResumeNext over catch, would be if I didn't want to introduce some closure.
An example of this would be if I had catch, and I was handling my error in this observable that I was using here, I was actually coming from outside and I was closing over it, it would still work the same, but I've introduced a closure here. So what I can do, is I can use onErrorResumeNext and no closure, same effect. Another way to handle errors is by retrying. To retry, you can use the retry operator, and you just give it a number that is the number of times you'd like to retry whatever observable the retry operator is operating on.
So in this case, I'm saying that I will let this fail three times before I allow the error to go ahead and propagate to my error handler. Let's go ahead and try that out. You'll see it ran once, twice, three times and then the error made it through to my handler. If I were to remove this 3, it will keep retrying forever. I'm not going to do that right now, because this bit here will go synchronously forever, and lock up my browser.
What if we want to add a delay to our retry? For that we can use the retryWhen operator. retryWhen gives us an observable of errors that occur in the observable that retryWhen is operating on. It expects us to return an observable that when it emits the first time, will cause the observable that retryWhen is operating on to retry. So to show this, I'm going to scrub my observable of errors into a delayed observable of about one second.
I'm going to run this, you'll see it goes on and on, and there's a one second delay between each failure. This is going to keep going on forever. So an interesting thing we can do with this is we can add a take, let's say we only want it to go five times. One, two, three, four, five, and it's done. Now you'll notice that this completed. That's because this observable completed successfully.
If I were to have this observable concat in a failure, move that down to another line get a little more readable, you should see it do the same thing, but now it's errored. RetryWhen is useful for doing things like retrying network connections for maybe a Web socket reconnection, or just doing some more advanced logic for your retry than what you'd be able to do with just a simple retry operator.