【问题标题】:Combine handle different type of publishers结合处理不同类型的发布者
【发布时间】:2020-11-03 11:15:20
【问题描述】:

我真的是Combine 的新手,我一直被这个问题困扰。我有基本的注册表单,如果一切正常,返回空响应,返回 200 代码;如果表单有一些注册失败,返回 442。

这是可以处理空响应并且工作正常的代码

extension Route where ResultType: EmptyResult {
    func emptyResult() -> AnyPublisher<Void, APIError> {
        return URLSession.shared.dataTaskPublisher(for: urlRequest)
            .print("EMPTY RESULT")
            .tryMap { data, response in
                guard let httpResponse = response as? HTTPURLResponse else { throw APIError.nonHttpResponse(description: "Not http resp") }
                let statusCode = httpResponse.statusCode
                
                guard (200..<300).contains(statusCode) else { throw APIError.nonHttpResponse(description: "bad response")
                }
                    return Void()
            }.mapError { error in
                print("Error \(error)")
                return .network(description: error.localizedDescription)
            }
            .eraseToAnyPublisher()
    }
}

但是,我如何返回其他类型的发布者?例如

struct CustomError: Decodable {
    let usernameError: String
    let emailError: String
}

我的网络通话:

    API.registration(name: name, email: email, password: password, schoolID: selectedSchool?.id ?? 0)
        .print("Registration")
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { (completion) in
            switch completion {
            case let .failure(error):
                print("ERROR \(error)")
            case .finished: break
            }
        }, receiveValue: { value in
            print(value)
        })
        .store(in: &disposables)

【问题讨论】:

  • 那么您的网络请求是否总是返回 CustomError 作为响应?
  • 并非总是如此,仅当存在表单验证问题时。如果没有表单验证问题,我会得到 200 个代码的空响应

标签: swift networking combine


【解决方案1】:

所以你有一个网络请求,如果请求成功,它会返回一个 200 响应和一个空正文,而如果是表单错误,它会返回一个特定的状态码和响应中的错误。

我建议将PublisherOutput 类型保留为Void,但是,如果出现表单错误,请解码错误并将其作为APIError 的一部分抛出。

struct LoginError: Decodable {
    let usernameError: String
    let emailError: String
}

enum APIError: Error {
    case failureStatus(code: Int)
    case login(LoginError)
    case nonHttpResponse(description: String)
    case network(Error)
}

func emptyResult() -> AnyPublisher<Void, APIError> {
    return URLSession.shared.dataTaskPublisher(for: urlRequest)
        .print("EMPTY RESULT")
        .tryMap { data, response in
            guard let httpResponse = response as? HTTPURLResponse else { throw APIError.nonHttpResponse(description: "Not http response") }
            let statusCode = httpResponse.statusCode

            guard (200..<300).contains(statusCode) else {
                if statusCode == 442 {
                    let loginError = try JSONDecoder().decode(LoginError.self, from: data)
                    throw APIError.login(loginError)
                } else {
                    throw APIError.failureStatus(code: statusCode)
                }
            }
            return Void()
        }.mapError { error in
            switch error {
            case let apiError as APIError:
                return apiError
            default:
                return .network(error)
        }
    }
        .eraseToAnyPublisher()
}

然后您可以通过switching 处理error in sink 中的特定错误:

API.registration(name: name, email: email, password: password, schoolID: selectedSchool?.id ?? 0)
    .print("Registration")
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { (completion) in
        switch completion {
        case let .failure(error):
            switch error {
            case .login(let loginError):
                print("Login failed, \(loginError.emailError), \(loginError.usernameError)")
            default:
                print(error)
            }
        case .finished: break
        }
    }, receiveValue: { value in
        print(value)
    })
    .store(in: &disposables)

【讨论】:

  • 太棒了!但是,还有一个问题。在我的 ViewModel 中,我看到只有网络错误被抛出,而不是我们解析的那个,当状态码为 442 时。也许 .mapError 之前覆盖了抛出的错误?
  • @AsMartynas 好吧,如果您的实际网络请求失败,那么是的,dataTaskPublisher 将失败并出现错误,因此将调用 mapError,而不是 tryMap。但是,dataTaskPublisher 因错误而失败意味着请求没有响应,因为请求本身失败。您需要调查您的请求失败的原因。
  • 这很奇怪,因为我可以在状态码中打印数据 if 语句并且 AuthError 被成功解码,所以似乎无论如何都会调用 mapError 。从日志中我看到 EMPTY RESULT:receive cancel,也许是这种情况?
  • @AsMartynas 实际上是我的错,如果您在tryMap 中抛出错误,该错误将传播到mapError,并且不会立即使发布者失败。检查我更新的答案,现在mapError 应该正确处理从tryMap 内部抛出的错误。
  • @AsMartynas 您只需要在sink 中将switch 超过error。检查我更新的答案。
猜你喜欢
  • 2021-07-10
  • 2021-06-11
  • 2018-07-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-30
  • 1970-01-01
  • 1970-01-01
  • 2019-05-04
相关资源
最近更新 更多