Given the following code:
enum MyError: Error {
case someError
}
myButton.publisher(for: .touchUpInside).tryMap({ _ in
if Bool.random() {
throw MyError.someError
} else {
return "we're in the else case"
}
})
.replaceError(with: "replaced Error")
.sink(receiveCompletion: { (completed) in
print(completed)
}, receiveValue: { (sadf) in
print(sadf)
}).store(in: &cancellables)
Whenever I tap the button, I get we're in the else case
until Bool.random()
is true - now an error is thrown. I tried different things, but I couldn't achieve to catch/replace/ignore the error and just continue after tapping the button.
In the code example I would love to have e.g. the following output
we're in the else case
we're in the else case
replaced Error
we're in the else case
...
instead I get finished
after the replaced error
and no events are emitted.
Edit
Given a publisher with AnyPublisher<String, Error>
, how can I transform it to a AnyPublisher<String, Never>
without completing when an error occurs, i.e. ignore errors emitted by the original publisher?
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.
AnyPublisher is a concrete implementation of Publisher that has no significant properties of its own, and passes through elements and completion values from its upstream publisher. Use AnyPublisher to wrap a publisher whose type has details you don't want to expose across API boundaries, such as different modules.
Overview. The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.
There was a WWDC movie mentioned, and I believe it's "Combine in Practice" from 2019, start watching around 6:24: https://developer.apple.com/wwdc19/721
Yes, .catch()
terminates the upstream publisher (movie 7:45) and replaces it with a given one in the arguments to .catch
thus usually resulting in .finished
being delivered when using Just()
as the replacement publisher.
If the original publisher should continue to work after a failure, a construct involving .flatMap()
is requried (movie 9:34). The operator resulting in a possible failure needs to be executed within the .flatMap
, and can be processed there if necessary. The trick is to use
.flatMap { data in
return Just(data).decode(...).catch { Just(replacement) }
}
instead of
.catch { return Just(replacement) } // DOES STOP UPSTREAM PUBLISHER
Inside .flatMap
you always replace the publisher and thus do not care if that replacement publisher is terminated by .catch
, since its already a replacement and our original upstream publisher is safe. This example is from the movie.
This is also the answer to your Edit: question, on how to turn a <Output, Error>
into <Output, Never>
, since the .flatMap
does not output any errors, its Never before and after the flatMap. All error-related steps are encapsulated in the flatMap.
(Hint to check for Failure=Never
: if you get Xcode autocompletion for .assign(to:)
then I believe you have a Failure=Never stream, that subscriber is not available otherwise.
And finally the full playground code
PlaygroundSupport.PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError: Error {
case someError
}
let cancellable = Timer.publish(every: 1, on: .main, in: .default)
.autoconnect()
.flatMap({ (input) in
Just(input)
.tryMap({ (input) -> String in
if Bool.random() {
throw MyError.someError
} else {
return "we're in the else case"
}
})
.catch { (error) in
Just("replaced error")
}
})
.sink(receiveCompletion: { (completion) in
print(completion)
PlaygroundSupport.PlaygroundPage.current.finishExecution()
}) { (output) in
print(output)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With