Reputation: 123
I am trying to set up a publisher that will publish a set of integers and at some point may fail. It's slightly contrived but hopefully illustrates principle. Example below.
enum NumberError: Int, Error {
case isFatal, canContinue
}
struct Numbers {
let p = PassthroughSubject<Int, NumberError>()
func start(max: Int) {
let errorI = Int.random(in: 1...max)
for i in (1...max) {
if errorI == i {
p.send(completion: .failure(NumberError.canContinue))
} else {
p.send(i)
}
}
p.send(completion: .finished)
}
}
I then subscribe using:
let n = Numbers()
let c = n.p
.catch {_ in return Just(-1)}
.sink(receiveCompletion: {result in
switch result {
case .failure:
print("Error")
case .finished:
print("Finished")
}
}, receiveValue: {
print($0)
})
n.start(max: 5)
This works in that it replaces errors with -1 but I would then like to continue receiving values. Does anyone know if this is possible? Having read and looked around it seems that flatMap may be the way to go but I can't work out what publisher to use in the closure? Any help much appreciated.
Upvotes: 7
Views: 2226
Reputation: 385580
I think you have an incorrect belief that a PassthroughSubject
can publish more outputs after publishing a failure. It cannot. After you call p.send(completion: ...)
, any calls to p.send(...)
will be ignored. Furthermore, if you subscribe to p
after you call p.send(completion: ...)
, p
will immediately complete the new subscription and not send any outputs.
So you can't send your error as .failure
if you want to send more values after. Instead, change your publisher's Output
type to Result<Int, NumberError>
and its Failure
type to Never
:
import Combine
enum NumberError: Int, Error {
case isFatal, canContinue
}
struct Numbers {
let p = PassthroughSubject<Result<Int, NumberError>, Never>()
func start(max: Int) {
let bad = (max + 1) / 2
for i in (1...max) {
if bad == i {
p.send(.failure(NumberError.canContinue))
} else {
p.send(.success(i))
}
}
p.send(completion: .finished)
}
}
But now you can't use catch
to handle the error, because it's not coming through as a failure. Instead, you can use map
:
let n = Numbers()
let c = n.p
.map({
switch $0 {
case .success(let i): return i
case .failure(_): return -1
}
})
.sink(receiveCompletion: {result in
switch result {
case .failure:
print("Error")
case .finished:
print("Finished")
}
}, receiveValue: {
print($0)
})
n.start(max: 5)
Output:
1
2
-1
4
5
Finished
Upvotes: 9