如何用Swift Combine处理含自定义错误信息的HTTP响应
Combine实现API错误处理的函数式方案
问题背景
假设某API处理无效请求时,会返回400段HTTP状态码及包含可读信息的JSON payload,比如针对已删除或不存在的内容,返回404状态码和{ "message": "Not Found" }。
已有不使用Publisher的async/await实现代码:
struct APIErrorResponse: Decodable, Error { let message: String } func request(request: URLRequest) async throws -> Post { let (data, response) = try await URLSession.shared.data(for: request) let statusCode = (response as! HTTPURLResponse).statusCode if 400..<500 ~= statusCode { throw try JSONDecoder().decode(APIErrorResponse.self, from: data) } return try JSONDecoder().decode(Post.self, from: data) }
现在需要用纯函数式代码简洁实现该逻辑,核心需求是:根据HTTPURLResponse.statusCode选择解码不同类型(错误类型则抛出),同时适配Combine的代码模式,替代下面的基础实现:
URLSession.shared.dataTaskPublisher(for: request) .map(\.data) .decode(type: Post.self, decoder: JSONDecoder()) .eraseToAnyPublisher()
函数式实现方案
我们可以通过Combine的操作符链式调用,拆分response和data的处理流程,完成状态码判断、错误解码和正常数据解码的逻辑:
struct APIErrorResponse: Decodable, Error { let message: String } func requestPublisher(for request: URLRequest) -> AnyPublisher<Post, Error> { URLSession.shared.dataTaskPublisher(for: request) // 同时处理data和response,完成状态校验与错误抛出 .tryMap { data, response in guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) } // 处理400-499客户端错误,解码并抛出API自定义错误 if 400..<500 ~= httpResponse.statusCode { let apiError = try JSONDecoder().decode(APIErrorResponse.self, from: data) throw apiError } // 校验2xx成功状态码(可选,根据API需求调整) guard 200..<300 ~= httpResponse.statusCode else { throw URLError(.badServerResponse) } return data } // 解码正常响应数据 .decode(type: Post.self, decoder: JSONDecoder()) .eraseToAnyPublisher() }
代码说明
tryMap操作符:同时获取data和response,集中完成状态码校验、错误解码逻辑,通过抛出错误实现流程分支,完全符合函数式无副作用的风格。- 类型安全校验:避免原代码中的强制解包,将
response转为HTTPURLResponse失败时抛出通用URLError。 - 可复用封装:如果需要在多个请求中复用错误处理逻辑,可以把校验逻辑抽成独立函数:
func validateResponse(data: Data, response: URLResponse) throws -> Data { guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) } if 400..<500 ~= httpResponse.statusCode { throw try JSONDecoder().decode(APIErrorResponse.self, from: data) } guard 200..<300 ~= httpResponse.statusCode else { throw URLError(.badServerResponse) } return data } // 调用时更简洁: func requestPublisher(for request: URLRequest) -> AnyPublisher<Post, Error> { URLSession.shared.dataTaskPublisher(for: request) .tryMap(validateResponse) .decode(type: Post.self, decoder: JSONDecoder()) .eraseToAnyPublisher() }
内容的提问来源于stack exchange,提问作者Ilias Karim
相关产品推荐
相关产品推荐

