【问题标题】:What is the best way to handle errors in Combine?在 Combine 中处理错误的最佳方法是什么?
【发布时间】:2019-10-05 19:07:03
【问题描述】:

我正在尝试使用以下代码将下载的 JSON 解码为结构。

static func request(url: URL) -> AnyPublisher<SomeDecodableStruct, Error> {
    return URLSession.shared.dataTaskPublisher(for: url)
        .map { $0.data }
        .decode(type: SomeDecodableStruct.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

但是,如果处理失败,我希望您返回有关请求处理失败或解码处理失败的信息。 因此,我定义了符合Error协议的FailureReason枚举如下。

enum FailureReason : Error {
    case sessionFailed(error: URLError)
    case decodingFailed
}

static func request(url: URL) -> AnyPublisher<SomeDecodableStruct, FailureReason> {
    // ???
}

如何定义满足FailureReasonrequest(url:)

【问题讨论】:

    标签: swift swiftui combine


    【解决方案1】:

    Combine 在错误方面是强类型的,因此您必须使用 mapError 将错误转换为正确的类型,或者像 RxSwift 一样草率并将所有内容衰减到 Error

    enum NetworkService {
      enum FailureReason : Error {
          case sessionFailed(error: URLError)
          case decodingFailed
          case other(Error)
      }
    
      static func request<SomeDecodable: Decodable>(url: URL) -> AnyPublisher<SomeDecodable, FailureReason> {
        return URLSession.shared.dataTaskPublisher(for: url)
          .map(\.data)
          .decode(type: SomeDecodable.self, decoder: JSONDecoder())
          .mapError({ error in
            switch error {
            case is Swift.DecodingError:
              return .decodingFailed
            case let urlError as URLError:
              return .sessionFailed(error: urlError)
            default:
              return .other(error)
            }
          })
          .eraseToAnyPublisher()
      }
    }
    

    【讨论】:

    • 很好的解决方案!只是一个问题,我看到第一件事是映射数据.map(\.data),所以dataTaskPublisher的输出响应被忽略了,那么如果某些API返回像HTTP 404这样的错误,我们怎么能抓住它?
    • @ChuckZHB 使用 catch 运算符和 Fail 发布者:.catch { Fail(...) }
    【解决方案2】:

    在这种情况下,我不会使用 Failure 类型而不是 Never 来声明发布者。否则,Publisher 将发送一个完成,其中包含它遇到的第一个错误并完全停止发布。制作Result 类型的Output 要好得多。在每个可能产生错误的步骤之后,您使用.mapError 将其映射到您的错误类型,最后捕获错误并返回Result.failure

    func request(url: URL) -> AnyPublisher<Result<SomeDecodableStruct, FailureReason>, Never> {
            return URLSession.shared.dataTaskPublisher(for: url)
                        .mapError { Error.sessionFailed(error: $0) }
                        .map { $0.data }
                        .decode(type: SomeDecodableStruct.self, decoder: JSONDecoder())
                        .map { Result<SomeDecodableStruct, FailureReason>.success($0)}
                        .mapError { _ in Error.decodingFailed }
                        .catch { Just<Result<SomeDecodableStruct, FailureReason>>(.failure($0)) }
                        .eraseToAnyPublisher()
        }
    

    【讨论】:

    • 感谢您的回答。使用Result 的想法很棒。我有个问题。源代码是否通过第二个mapError.sessionFailure 转换为decodingFailure
    • .mapError 将此时(在此示例中)可以抛出的错误更改为任何其他类型的错误类型。由于您的 FailureReason 已将 sessionFailure 声明为具有关联类型的 URLError,因此您需要在初始化此案例时将其传入。
    • 除非您正在回收这个发布者(即它在 flatMap 内部),否则我不会将错误具体化为结果。即使这样,通常也有比使用物化或手动物化结果更好的模式来捕捉错误。
    • @JoshHomann 我不会遇到以这种方式手动实现错误的问题。
    猜你喜欢
    • 2017-11-13
    • 1970-01-01
    • 2015-06-12
    • 2017-12-26
    • 2016-03-27
    • 2020-06-18
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    相关资源
    最近更新 更多