Error Handling in Combine Explained
Using code examples to show how to beat those failing cases
When getting started with Combine you’ll quickly run into error handling issues. Each Combine stream receives either a value or an error, and unlike frameworks like RxSwift, you need to be specific about the expected error type.
To prepare you for these cases, in this piece I’ll go over the options available in Combine to catch, ignore, and handle errors on a stream. We will also cover some important things you need to know when an error occurs on your stream.
Just getting started with Combine? You might want to first take a look at Getting started with the Combine framework in Swift or my Combine Playground.
Combine Streams and Typed Errors
A big difference between a framework like RxSwift and Combine is the requirement of typed error definitions in streams. If we compare the
Observable with its Combine equivalent
AnyPublisher, we can see the difference in the type declaration.
public class Observable<Element> : ObservableType
struct AnyPublisher<Output, Failure> where Failure : Error
AnyPublisher requires us to specify the
Failure error type while the
Observable only takes the generic
Swift requires us to think about error handling which we can take as something good. However, it does not hold us back from defining the expected type as just
Swift.Error, which basically comes down to the same behavior as in RxSwift.
Once you do require your stream to expect a certain error type, you’ll run into casting errors as each operator needs to return the same error type as the leading stream. Let’s dive into the Combine operators for error handling.
Mapping errors using mapError
To map an error to the expected error type we can use the
mapError operator. In the following example, we have a passthrough subject which expects a URL output and a
RequestError error type.
Once we start mapping this stream into a
URLSessionDataTaskPublisher we immediately get an error pointing out the error type mismatch.
In this case, the solution is as simple as using the
mapError operator which will wrap the
URLError into a
RequestError using the session error case we defined earlier.
Using the retry operator
In the above example, we’ve used a
URLSessionDataTaskPublisher. You might want to use the retry operator before actually accepting an error when working with data requests. It takes the number of retries to take before letting the stream actually fail.
If you want to catch errors early and ignore them after you can use the
catch operator. This operator allows you to return a default value for if the request failed. Examples of this could be:
- An empty array for search results
- A default image placeholder if the image request failed
The latter is the one we will use in our example.
Using replaceError instead of catch
ReplaceError vs Catch: both operators seem very similar. The big difference is that the
replaceError(:) operator is completely ignoring the error. As in the above example, we're doing nothing more than returning the placeholder
notFoundImage in the case of an error.
We could simplify this by using the replace error operator to directly map any errors into our placeholder image:
When the assign(to:on:) operator is unavailable
A common example in which you’ll need to map errors is when you try to assign an outcoming value to a property of an object. You’ll try to use the autocompletion and you find out that the
assign(to:on:) operator is unavailable. The following error will occur if you are forced to write the code either way:
Referencing instance method ‘assign(to:on:)’ on ‘Publisher’ requires the types ‘RequestError’ and ‘Never’ be equivalent
You can fix this by either catching the error as in explained in the above example or by simply using the
assertNoFailure operator. This operator will raise a fatalError and should, therefore, only be used if it's a programming error. If an error is expected you should always use the catch operator instead.
We’ve covered a lot about error handling in Combine which should be enough to make you beat all those failing cases! Make sure to handle errors accordingly and do not simply ignore them. The unhappy flow is just as important to your users as a happy flow.
If you’d like to play around with the things you just have learned, take a look at my Swift Combine Playground which includes a page about error handling in Combine.
To read more about Swift Combine, take a look at my other Combine blog posts: