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?
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)
}