为什么我们在 Combine 中没有 tryFlatMap 运算符?

Why don't we have tryFlatMap operator in Combine?

我们应该用什么来代替?
我很惊讶之前没有人问过这个问题。

严格来说您不需要 tryFlatMap,因为 flatMap 的转换 return 是一个发布者。如果发现错误,您可以在转换闭包中使用 do/catch 和 return 一个 Fail 发布者。

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

如果愿意,您可以编写自己的 tryFlatMap 运算符:

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 { [=11=] as Error }
                    .eraseToAnyPublisher()
            } catch {
                return Fail(outputType: Pub.Output.self, failure: error)
                    .eraseToAnyPublisher()
            }
        })
    }
}

然后像这样使用它:

upstream
    .tryFlatMap { try someFunction(of: [=12=]) }

还有一个解决方案。
tryFlatMap = tryMap + flatMap。

例如
整个故事开始于我只是想打开一个可选的包装。如果它是零,我希望它只是失败。

let upstream = Just(siteURL)
    .tryMap { url -> URL in
        guard let url = url else { throw Errors.invalidSiteURL }
        return url
    }
    .flatMap {
        URLSession.shared.dataTaskPublisher(for: [=10=]).mapError { [=10=] as Error }
    }

[编辑]像这样也有效:

let container = Just(siteURL)
    .tryMap { url -> URLSession.DataTaskPublisher in
        guard let url = url else { throw Errors.invalidSiteURL }
        return URLSession.shared.dataTaskPublisher(for: url)
    }
    .map { [=11=].mapError { [=11=] as Error } }
    .switchToLatest()

乱七八糟的错误类型有点烦人。