Search code examples
swiftcombine

Why can't I use .flatMap() after .tryMap() in Swift Combine?


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


Solution

  • 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...