Error Handling With Observable Sequences
One of the most difficult tasks in asynchronous programming is dealing with errors. Unlike interactive style programming, we cannot simply use the try/catch/finally approach that we use when dealing with blocking code.
These actions mirror exactly our Observer
class which has the following contract for handing zero to infinite items with onNext
and optionally handling either an Error
with onError
or successful completion with onCompleted
.
But the try/catch/finally approach won't work with asynchronous code. Instead, we have a myriad of ways of handling errors as they occur, and ensure proper disposal of resources.
For example, we might want to do the following:
swallow the error and switch over to a backup Observable to continue the sequence
swallow the error and emit a default item
swallow the error and immediately try to restart the failed Observable
swallow the error and try to restart the failed Observable after some back-off interval
We'll cover each of those scenarios and more in this section.
Catching Errors ##
The first topic is catching errors as they happen with our streams. In the Reactive Extensions, any error is propagated through the onError
channel which halts the sequence. We can compensate for this by using the catch
operator, at both the class and instance level.
Using the class level catch
method, we can catch errors as they happen with the current sequence and then move to the next sequence should there be an error. For example, we could try getting data from several URLs, it doesn't matter which since they all have the same data, and then if that fails, default to a cached version, so an error should never propagate. One thing to note is that if get('url')
calls succeed, then it will not move onto the next sequence in the list.
We also have an instance version of catch
which can be used two ways. The first way is much like the example above, where we can take an existing stream, catch the error and move onto the next stream or Promise
.
The other overload of catch
allows us to inspect the error as it comes in so we can decide which route to take. For example, if an error status code of 500 comes back from our web server, we can assume it is down and then use a cached version.
This isn't the only way to handle errors as there are plenty of others as you'll see below.
Ignoring Errors with onErrorResumeNext
##
onErrorResumeNext
##The class based onErrorResumeNext
continues a stream that is terminated normally or by an Error
with the next stream or Promise
. Unlike catch
, onErrorResumeNext
will continue to the next sequence regardless of whether the previous was in error or not. To make this more concrete, let's use a simple example of mixing error sequences with normal sequences.
The instance based onErrorResumeNext
is similar to the class based version, the only difference being that it is attached to the prototype, but can take another sequence or Promise
and continue.
Retrying Sequences ##
When catching errors isn't enough and we want to retry our logic, we can do so with retry
or retryWhen
operators. With the retry
operator, we can try a certain operation a number of times before an error is thrown. This is useful when you need to get data from a resource which may have intermittent failures due to load or any other issue.
Let's take a look at a simple example of trying to get some data from a URL and giving up after three tries.
In the above example, it will give up after three tries and thus call onError
if it continues to fail after the third try. We can remedy that by adding catch
to use an alternate source.
The above case retries immediately upon failure. But what if you want to control when a retry happens? We have the retryWhen
operator which allows us to deeply control when the next try happens. We incrementally back off trying again by using the following method:
Ensuring Cleanup with Finally ##
We've already covered the try/catch part of try/catch/finally, so what about finally? We have the finally
operator which calls a function after the source sequence terminates gracefully or exceptionally. This is useful if you are using external resources or need to free up a particular variable upon completion.
In this example, we can ensure that our WebSocket
will indeed be closed once the last message is processed.
But we can do a better job in terms of managing resources if need be by using the using
method.
Ensuring Resource Disposal ##
As stated above, finally
can be used to ensure proper cleanup of any resources or perform any side effects as necessary. There is a cleaner approach we can take by creating a disposable wrapper around our object with a dispose
method so that when our scope is complete, then the resource is automatically disposed through the using
operator.
Delaying Errors with mergeDelayError
##
mergeDelayError
##Another issue may arise when you are dealing with flattening sequences into a single sequence and there may be errors along the way. We want a way to flatten without being interrupted by one of our sources being in error. This is much like the other operator mergeAll
but the main difference is, instead of immediately bubbling up the error, it holds off until the very end.
To illustrate, we can create this little sample that has an errored sequence in the middle when it is trying to flatten the sequences.
Further Reading ##
Last updated
Was this helpful?