Reputation: 766
I am studying and trying out a few stuff with Combine to apply on my own and came into the following situation with this contrived example..
let sequencePublisher = [70, 5, 17].publisher
var cancellables = [AnyCancellable]()
sequencePublisher
// .spellOut()
.flatMap { query -> URLSession.DataTaskPublisher in
return URLSession.shared.dataTaskPublisher(for: URL(string: "http://localhost:3000?q=\(query)")!)
}
.compactMap { String(data: $0.data, encoding: .utf8) }
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
default: print("finish")
}
}) { value in
print(value)
}
.store(in: &cancellables)
I have a sequence publisher that emits 3 Integers and I pass it through flatMap
and send a Get request request to my local API that simply returns back the same value it got embedded in a string.
It all works fine, I get all 3 API responses in sink, as long as I don't uncomment the spellOut()
custom operator, this operator is supposed to fail if the number is smaller than 6, here is what it does:
enum ConversionError: LocalizedError {
case lessThanSix(Int)
var errorDescription: String? {
switch self {
case .lessThanSix(let n):
return "could not convert number -> \(n)"
}
}
}
extension Publisher where Output == Int {
func spellOut() -> Publishers.TryMap<Self, String> {
tryMap { n -> String in
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
guard n > 6, let spelledOut = formatter.string(from: n as NSNumber) else { throw ConversionError.lessThanSix(n) }
return spelledOut
}
}
}
The code doesn't even compile if I add another map
operator before flatMap it works, but with a tryMap it just says
No exact matches in call to instance method 'flatMap'
Is there any way of achieving this or why is it not allowed?
Thank you in advance for the answers
Upvotes: 5
Views: 4957
Reputation: 3157
In case the error types already match, another point of failure can be, when you work with any Publisher
as return values. Then you need to call eraseToAnyPublisher()
on the publishers - the first one and the one returned from the flatMap
closure.
anyPublisher.eraseToAnyPublisher()
.flatMap { value in
anotherPublisher.eraseToAnyPublisher()
}
Upvotes: 0
Reputation: 151
You have to map the error after the tryMap.
publisher
.tryMap({ id in
if let id = id { return id } else { throw MyError.unknown("noId") }
})
.mapError { $0 as? MyError ?? MyError.unknown("noId") }
.flatMap { id -> AnyPublisher<Model, MyError> in
fetchDataUseCase.execute(id: id)
}
.eraseToAnyPublisher()
Upvotes: 0
Reputation: 49590
The problem here is that FlatMap
requires the returned publisher created in its closure to have the same Failure
type as its upstream (unless upstream has a Never
failure).
So, a Sequence
publisher, like:
let sequencePublisher = [70, 5, 17].publisher
has a failure type of Never
and all works.
But TryMap
, which is what .spellOut
operator returns, has a failure type of Error
, and so it fails, because DataTaskPublisher
has a URLError
failure type.
A way to fix is to match the error type inside the flatMap
:
sequencePublisher
.spellOut()
.flatMap { query in
URLSession.shared.dataTaskPublisher(for: URL(...))
.mapError { $0 as Error }
}
// etc...
Upvotes: 10