如果使用 Combine 和 Swift 第一次解码失败,则解码另一个响应
Decode another response if first decoding failed using Combine and Swift
我有以下型号:
struct Response: Decodable {
let message: String
}
struct ErrorResponse: Decodable {
let errorMessage: String
}
enum APIError: Error {
case network(code: Int, description: String)
case decoding(description: String)
case api(description: String)
}
我正在尝试获取 url 并使用此流程解析 JSON 响应:
func fetch(url: URL) -> AnyPublisher<Response, APIError> {
URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
// #1 URLRequest fails, throw APIError.network
.mapError { .network(code: [=12=].code.rawValue, description: [=12=].localizedDescription) }
// #2 try to decode data as a `Response`
.tryMap { JSONDecoder().decode(Response.self, from: [=12=].data) }
// #3 if decoding fails, decode as an `ErrorResponse`
// and throw `APIError.api(description: errorResponse.errorMessage)`
// #4 if both fail, throw APIError.decoding
// #5 return
.eraseToAnyPublisher()
}
我对 #3
有疑问:如何解码 tryMap
部分之后的原始数据?
似乎我可以访问的唯一值是来自 tryMap
的错误,但我需要原始数据来解码 ErrorRepsonse
.
注意:不幸的是,错误响应带有 200 状态,区分它们的唯一方法是解码它们。
您可以使用 flatMap
并在其中处理解码:
URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
// #1 URLRequest fails, throw APIError.network
.mapError {
APIError.network(code: [=10=].code.rawValue, description: [=10=].localizedDescription)
}
.flatMap { data -> AnyPublisher<Response, APIError> in
// #2 try to decode data as a `Response`
if let response = try? JSONDecoder().decode(Response.self, from: data) {
return Just(response).setFailureType(to: APIError.self)
.eraseToAnyPublisher()
}
do {
// #3 if decoding fails, decode as an `ErrorResponse`
let error = try decoder.decode(ErrorResponse.self, from: data)
// and throw `APIError.api(description: errorResponse.errorMessage)`
return Fail(error: APIError.api(description: errorResponse.errorMessage))
.eraseToAnyPublisher()
} catch {
// #4 if both fail, throw APIError.decoding
return Fail(error: APIError.decoding(description: error.localizedDescription))
.eraseToAnyPublisher()
}
}
编辑
如果您想以“纯粹的”合并方式执行此操作,那么您仍然希望使用 flatMap
来访问原始数据并回避最初可能出现的网络错误,然后使用tryCatch
处理失败路径
请注意,第 4 步位于第 3 步的两个部分之间:
URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
// #1 URLRequest fails, throw APIError.network
.mapError {
APIError.network(code: [=11=].code.rawValue, description: [=11=].localizedDescription)
}
.flatMap { v in
Just(v)
// #2 try to decode data as a `Response`
.decode(type: Response.self, decoder: JSONDecoder())
// #3 if decoding fails,
.tryCatch { _ in
Just(v)
// #3.1 ... decode as an `ErrorResponse`
.decode(type: ErrorResponse.self, decoder: JSONDecoder())
// #4 if both fail, throw APIError.decoding
.mapError { _ in APIError.decoding(description: "error decoding") }
// #3.2 ... and throw `APIError.api
.tryMap { throw APIError.api(description: [=11=].errorMessage) }
}
// force unwrap is not terrible here, since you know
// that `tryCatch` only ever throws APIError
.mapError { [=11=] as! APIError }
}
我有以下型号:
struct Response: Decodable {
let message: String
}
struct ErrorResponse: Decodable {
let errorMessage: String
}
enum APIError: Error {
case network(code: Int, description: String)
case decoding(description: String)
case api(description: String)
}
我正在尝试获取 url 并使用此流程解析 JSON 响应:
func fetch(url: URL) -> AnyPublisher<Response, APIError> {
URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
// #1 URLRequest fails, throw APIError.network
.mapError { .network(code: [=12=].code.rawValue, description: [=12=].localizedDescription) }
// #2 try to decode data as a `Response`
.tryMap { JSONDecoder().decode(Response.self, from: [=12=].data) }
// #3 if decoding fails, decode as an `ErrorResponse`
// and throw `APIError.api(description: errorResponse.errorMessage)`
// #4 if both fail, throw APIError.decoding
// #5 return
.eraseToAnyPublisher()
}
我对 #3
有疑问:如何解码 tryMap
部分之后的原始数据?
似乎我可以访问的唯一值是来自 tryMap
的错误,但我需要原始数据来解码 ErrorRepsonse
.
注意:不幸的是,错误响应带有 200 状态,区分它们的唯一方法是解码它们。
您可以使用 flatMap
并在其中处理解码:
URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
// #1 URLRequest fails, throw APIError.network
.mapError {
APIError.network(code: [=10=].code.rawValue, description: [=10=].localizedDescription)
}
.flatMap { data -> AnyPublisher<Response, APIError> in
// #2 try to decode data as a `Response`
if let response = try? JSONDecoder().decode(Response.self, from: data) {
return Just(response).setFailureType(to: APIError.self)
.eraseToAnyPublisher()
}
do {
// #3 if decoding fails, decode as an `ErrorResponse`
let error = try decoder.decode(ErrorResponse.self, from: data)
// and throw `APIError.api(description: errorResponse.errorMessage)`
return Fail(error: APIError.api(description: errorResponse.errorMessage))
.eraseToAnyPublisher()
} catch {
// #4 if both fail, throw APIError.decoding
return Fail(error: APIError.decoding(description: error.localizedDescription))
.eraseToAnyPublisher()
}
}
编辑
如果您想以“纯粹的”合并方式执行此操作,那么您仍然希望使用 flatMap
来访问原始数据并回避最初可能出现的网络错误,然后使用tryCatch
处理失败路径
请注意,第 4 步位于第 3 步的两个部分之间:
URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
// #1 URLRequest fails, throw APIError.network
.mapError {
APIError.network(code: [=11=].code.rawValue, description: [=11=].localizedDescription)
}
.flatMap { v in
Just(v)
// #2 try to decode data as a `Response`
.decode(type: Response.self, decoder: JSONDecoder())
// #3 if decoding fails,
.tryCatch { _ in
Just(v)
// #3.1 ... decode as an `ErrorResponse`
.decode(type: ErrorResponse.self, decoder: JSONDecoder())
// #4 if both fail, throw APIError.decoding
.mapError { _ in APIError.decoding(description: "error decoding") }
// #3.2 ... and throw `APIError.api
.tryMap { throw APIError.api(description: [=11=].errorMessage) }
}
// force unwrap is not terrible here, since you know
// that `tryCatch` only ever throws APIError
.mapError { [=11=] as! APIError }
}