【问题标题】:Preserving Failure Type with Combine's tryMap使用 Combine 的 tryMap 保留失败类型
【发布时间】:2020-04-03 21:00:01
【问题描述】:

我正在使用 Combine 来编写一个简单的网络爬虫。我正在尝试将返回的数据映射到 HTML 字符串,在每个可能的故障点抛出 ScraperErrors。最后,我想将此字符串传递给我的htmlSubject,即PassthroughSubject<String, ScraperError>,以进行进一步处理。

    urlSubscription = URLSession.shared
            .dataTaskPublisher(for: url)
            .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
                ScraperError.unreachableSite
            }
            .tryMap { (data, response) -> String in
                guard let html = String(data: data, encoding: .utf8) else {
                    throw ScraperError.readFailed
                }

                return html
            }
            .subscribe(htmlSubject) // <-- Not allowed because failure type is now Error

但是,我发现.tryMap 正在将我的ScraperError 擦除为常规的Error,从而阻止我将htmlSubject 链接到末尾:

实例方法 'subscribe' 需要类型 'Error' 和 'ScraperError' 是等价的。

我是否有明显的方法来解决这个问题,或者我是否在概念上被绊倒了?我认为这条链是一个大型函数中的构建块,将&lt;(Data, URLResponse), URLError&gt; 映射到&lt;String, ScraperError&gt;

感谢任何帮助。

【问题讨论】:

标签: swift reactive-programming combine


【解决方案1】:

tryMap之后使用mapError转换回ScraperError

urlSubscription = URLSession.shared
    .dataTaskPublisher(for: url)
    .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
        ScraperError.unreachableSite
    }
    .tryMap { (data, response) -> String in
        guard let html = String(data: data, encoding: .utf8) else {
            throw ScraperError.readFailed
        }

        return html
    }
    .mapError { $0 as! ScraperError }
    .subscribe(htmlSubject)

如果您不想使用as!,则必须选择其他情况来映射:

    .mapError { $0 as? ScraperError ?? ScraperError.unknown }

如果你也不喜欢这样,你可以使用flatMap 而不是Result&lt;String, ScraperError&gt;.Publisher

urlSubscription = URLSession.shared
    .dataTaskPublisher(for: url)
    .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
        ScraperError.unreachableSite
}
.flatMap { (data, response) -> Result<String, ScraperError>.Publisher in
    guard let html = String(data: data, encoding: .utf8) else {
        return .init(.readFailed)
    }
    return .init(html)
}
.subscribe(htmlSubject)

【讨论】:

  • 干杯 Rob,我真的很喜欢 .flatMap 解决方案。很好的答案!
【解决方案2】:

我发现将 Rob 的 flatMap 方法包装到扩展中时生成的代码更具可读性:

extension Publisher {
    func flatMapResult<T>(_ transform: @escaping (Self.Output) -> Result<T, Self.Failure>) -> Publishers.FlatMap<Result<T, Self.Failure>.Publisher, Self> {
        self.flatMap { .init(transform($0)) }
    }
}

上面的代码示例将变为:

urlSubscription = URLSession.shared
    .dataTaskPublisher(for: url)
    .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
        ScraperError.unreachableSite
    }
    .flatMapResult { (data, response) -> Result<String, ScraperError> in
        guard let html = String(data: data, encoding: .utf8) else {
            return .failure(.readFailed)
        }
        return .success(html)
    }
    .subscribe(htmlSubject)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 1970-01-01
    • 2020-09-15
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    • 2021-11-20
    相关资源
    最近更新 更多