You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:31:39