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

The AnyPublisher requires us to specify the Failure error type while the Observable only takes the generic Element type.

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.

Error handling in Swift Combine using mapError
Error handling in Swift Combine using mapError
Error handling in Swift Combine using mapError

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.

Catching errors

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.


Conclusion

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:


Originally published at SwiftLee.

More posts and updates: @twannl

Better Programming

Advice for programmers.

Antoine van der lee 🇳🇱

Written by

iOS Developer @WeTransfer — Follow me on twitter.com/twannl for more tips & tricks — Blogging weekly at https://avanderlee.com — Clap & hold for a surprise!

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade