【问题标题】:handling HTTP status code with URLSession and Combine使用 URLSession 和 Combine 处理 HTTP 状态码
【发布时间】:2020-04-15 02:37:37
【问题描述】:

我正在尝试处理来自DataTaskPublisher 的响应,读取其响应状态代码。

当状态码大于 299 时,我想返回一个 ServiceError 类型作为失败。在我看到的每个示例中,我都使用了.mapError.catch... 在这种特定情况下,来自.flatMap,我真的不知道如何处理发布者响应以返回错误TResponse...

    return URLSession.DataTaskPublisher(request: urlRequest, session: .shared)
        .mapError{error in return ServiceError.request}
        .flatMap{ data, response -> AnyPublisher<TResponse, ServiceError> in

            if let httpResponse = response as? HTTPURLResponse,
                (200...299).contains(httpResponse.statusCode){

                return Just(data)
                    .decode(type: TResponse.self, decoder: JSONDecoder())
                    .mapError{error in return ServiceError.decode}
                    .eraseToAnyPublisher()
            }else{
                //???? HOW TO HANDLE THE ERROR?
            }
        }
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()

【问题讨论】:

    标签: swift urlsession combine


    【解决方案1】:
    enum ServiceErrors: Error { 
        case internalError(_ statusCode: Int)
        case serverError(_ statusCode: Int)
    }
        
    return URLSession.shared.dataTaskPublisher(for: urlRequest)
                .tryMap { data, response in
                    guard let httpResponse = response as? HTTPURLResponse,
                        200..<300 ~= httpResponse.statusCode else {
                            switch (response as! HTTPURLResponse).statusCode {
                            case (400...499):
                                throw ServiceErrors.internalError((response as! HTTPURLResponse).statusCode)
                            default:
                                throw ServiceErrors.serverError((response as! HTTPURLResponse).statusCode)
                            }
                    }
                    return data
                }
                .mapError { $0 as! ServiceErrors }
                .decode(type: T.self, decoder: JSONDecoder())
                .receive(on: RunLoop.main)
                .eraseToAnyPublisher()
    

    注意:我依靠this 链接来制作我的错误处理程序。

    【讨论】:

      【解决方案2】:

      如果我正确理解了你的目标,你需要类似的东西

      }else{
          return Fail(error: ServiceError.badServiceReply)
                    .eraseToAnyPublisher()
      }
      

      简单示例:

      URLSession.shared
          .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
          .receive(on: DispatchQueue.main)
          .flatMap { _ in
              Fail(error: URLError(URLError.unsupportedURL)).eraseToAnyPublisher()
          } //for the sake of the demo
          .replaceError(with: "An error occurred") //this sets Failure to Never
          .assign(to: \.stringValue, on: self)
          .store(in: &cancellableBag)
      

      由于重新映射到Failpublisher,将始终分配字符串“发生错误”

      【讨论】:

        【解决方案3】:

        您可以在这里使用tryMap

        URLSession.shared.dataTaskPublisher(for: urlRequest)
            .mapError { error in return ServiceError.request }
            .tryMap { (data, response) throws -> TResponse in
                guard let httpResponse = response as? HTTPURLResponse,
                      200...299 ~= httpResponse.statusCode else {
                          throw ServiceError.invalidStatusCode((response as? HTTPURLResponse)?.statusCode ?? 0)
                      }
                return try JSONDecoder().decode(TResponse.self, from: data)
            }
            .receive(on: RunLoop.main)
            .eraseToAnyPublisher()
        

        这也将简化您的代码,因为您不需要创建额外的发布者。

        我还对原始代码做了一些其他改进:

        • 使用URLSession.shared.dataTaskPublisher(for: urlRequest) 而不是URLSession.DataTaskPublisher(request: urlRequest, session: .shared)
        • 使用~= 运算符检查状态码
        • 使用guard 而不是let,因为它更惯用了

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-07-14
          • 2022-06-21
          • 2021-03-15
          • 2015-08-24
          • 2021-10-16
          • 2019-09-18
          • 1970-01-01
          • 2021-12-14
          相关资源
          最近更新 更多