Search code examples
swiftcombine

Why don't we have tryFlatMap operator in Combine?


And what shall we use instead?
I'm surprised that nobody asked this before.


Solution

  • You don't strictly need tryFlatMap, because flatMap's transform returns a publisher. You can use do/catch inside the transform closure and return a Fail publisher if you catch an error.

    import Combine
    
    func someFunction(of i: Int) throws -> AnyPublisher<Int, Error> {
        return Just(i + 1)
            .setFailureType(to: Error.self)
            .eraseToAnyPublisher()
    }
    
    let upstream: AnyPublisher<Int, Error> = Just(100)
        .setFailureType(to: Error.self)
        .eraseToAnyPublisher()
    
    upstream
        .flatMap({ i -> AnyPublisher<Int, Error> in
            do {
                return try someFunction(of: i).eraseToAnyPublisher()
            } catch {
                return Fail(error: error).eraseToAnyPublisher()
            }
        })
    

    You can write your own tryFlatMap operator if you like:

    extension Publisher {
        func tryFlatMap<Pub: Publisher>(
            maxPublishers: Subscribers.Demand = .unlimited,
            _ transform: @escaping (Output) throws -> Pub
        ) -> Publishers.FlatMap<AnyPublisher<Pub.Output, Error>, Self> {
            return flatMap(maxPublishers: maxPublishers, { input -> AnyPublisher<Pub.Output, Error> in
                do {
                    return try transform(input)
                        .mapError { $0 as Error }
                        .eraseToAnyPublisher()
                } catch {
                    return Fail(outputType: Pub.Output.self, failure: error)
                        .eraseToAnyPublisher()
                }
            })
        }
    }
    

    And then use it like this:

    upstream
        .tryFlatMap { try someFunction(of: $0) }