使用 URLSession 和 Combine 处理 HTTP 状态码
handling HTTP status code with URLSession and Combine
我正在尝试处理来自 DataTaskPublisher
读取其响应状态代码的响应。
当状态代码大于 299 时,我想 return 一个 ServiceError
类型为失败。在我见过的每个示例中,我都使用了 .mapError
和 .catch
... 在这种特定情况下,来自 .flatMap
,我真的不知道如何处理发布者响应 return 错误而不是 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()
如果我正确理解了你的目标,你需要像
这样的东西
}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)
将始终分配字符串 "An error occurred",因为重新映射到 Fail
发布者
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 { [=10=] as! ServiceErrors }
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
注意: 我依靠 this link 来制作我的错误处理程序。
您可以在此处使用 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
,因为它更惯用
我正在尝试处理来自 DataTaskPublisher
读取其响应状态代码的响应。
当状态代码大于 299 时,我想 return 一个 ServiceError
类型为失败。在我见过的每个示例中,我都使用了 .mapError
和 .catch
... 在这种特定情况下,来自 .flatMap
,我真的不知道如何处理发布者响应 return 错误而不是 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()
如果我正确理解了你的目标,你需要像
这样的东西}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)
将始终分配字符串 "An error occurred",因为重新映射到 Fail
发布者
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 { [=10=] as! ServiceErrors }
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
注意: 我依靠 this link 来制作我的错误处理程序。
您可以在此处使用 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
,因为它更惯用