Error Handling with RxSwift
In this article, I’m planning to cover the two primary ways of error handing in RxSwift, error recovery using retries, and returning a default(or cached) value.
Before we get started, let’s briefly talk about why we might need to handle RxSwift errors. Say we are observing network response using an observable sequence. If we lose internet connection, the observable sequence errors out. When an observable errors out, error subscriptions are notified and all subscriptions are then disposed of. The subscription updating the UI will stop working in this case and there will be no future updates when we regain the internet connection. This is a common pitfall that can be avoided by handling the errors either by retrying or returning a default value so the observable chain doesn’t error out and future updates can continue.
For all operators that accept closures, when we want to error out inside the operator closure, we can just throw the error as in regular swift code. RxSwift transforms any error thrown from within the closure into an error event that terminates the observable
- Catch: Recover from the error with a default value
2. Retry: retry for limited (or unlimited number of times)
Handle errors with catch
In RxSwift there are two main operators to catch errors:
func catchError(_ handler:) -> RxSwift.Observable<Self.E>
This operator gives you the capability of returning a completely different observable chain. We can use it in situations, say for example when we need to return a previously cached value if the observable errors out
func catchErrorJustReturn(_ element:) -> RxSwift.Observable<Self.E>
This operator ignores the error and returns a predefined value.
It’s much more limited than the previous one as it’s not possible to return a value for a given type of error — the same value is returned for any error, no matter what the error is.
Retrying on error
When a retry operator is used and an observable errors out, the observable will repeat itself. It’s important to remember that retry means repeating the entire task inside the observable.
func retry() -> Observable<Element>
This operator will repeat the observable an unlimited number of times until it returns successfully.
This might sound like a robust idea, but it’s resource-heavy, and it’s seldom recommended to retry for an unlimited number of times if there’s no valid reason for doing it.
func retry(_ maxAttemptCount:) -> Observable<Element>
Here the observable is repeated for the specified number of times.
func retryWhen(_ notificationHandler:) -> Observable<Element>
This is a very powerful operator used for some advanced retry situations
The notificationHandler is of type TriggerObservable. The trigger observable can be either a plain Observable or a Subject and is used to trigger the retry at arbitrary times.
Say we want to develop a strategy of increment delay before retrying. In regular imperative code, this would imply the creation of some abstractions, perhaps wrapping the task in an Operation, or creating a tailored wrapper around Grand Central Dispatch — but with RxSwift, the solution is a short block of code.
Before creating the final result, consider what the inner observable (the trigger) should return, taking into consideration that the type can be ignored and that the trigger can be of any type.
This observable has to be combined with the one that returns errors from the original observable. So when an error arrives as event, the combination of these observables will also receive the current index of the event.
You can achieve this by calling enumerated() on the observable and then using flatMap. The enumerated() method returns a new observable that sends tuples of the original observable’s values and their index.
Now the original error observable, and the one defining how long the delay should be before retrying, are combined.
Here’s a code snippet of what this might look like
Hope this article was helpful in briefly describing the different ways to handle errors in RxSwift. Please feel free to comment in case you have any questions or suggestions. Thanks for reading!