无法通过 Combine 发布 API 请求

Trouble posting API request with Combine

我是 Combine 游戏的新手,正在尝试弄清楚如何泛化 HTTP POST 请求。

我创建了以下 APIService class 来扩展个人资源服务:

import Foundation
import Combine

class APIService {
  let decoder: JSONDecoder
  let session: URLSession
  
  init(session: URLSession = URLSession.shared, decoder: JSONDecoder = JSONDecoder()) {
    self.decoder = decoder
    self.session = session
  }
}

// MARK: JSON API
extension APIService {
  struct Response<T> {
    let response: HTTPURLResponse
    let value: T
  }
  
  func post<T: Codable>(
    payload: T,
    url: URL
  ) -> AnyPublisher<Response<T>, APIError> {
    return Just(payload)
      .setFailureType(to: APIError.self) // <<< THIS WAS MISSING!
      .encode(encoder: JSONEncoder())
      .flatMap({ [weak self] payload -> AnyPublisher<Data, Error> in
        guard let self = self else {
          return Fail(error: .default("Failing to establish self.")).eraseToAnyPublisher()
        }
        var request = URLRequest(url: url)
        request.httpMethod = Methods.post
        request.setValue(
          Mimetypes.json,
          forHTTPHeaderField: Headers.contentType
        )
        request.httpBody = payload
        return self.session
          .dataTaskPublisher(
            for: request
          )
          .tryMap { response -> Response<T> in
            let value = try self.decoder.decode(T.self, from: response.data)
            return Response(
              value: value,
              response: response.response
            )
          }
          .mapError { error in
            return APIError.default(error.localizedDescription)
          }
          .receive(on: DispatchQueue.main)
          .eraseToAnyPublisher()
      })
      .eraseToAnyPublisher()
  }
}

然而,这个 class 不会在 post 函数处出现以下错误。

Type of expression is ambiguous without more context

作为 Swift 的新手,尤其是 Combine,不幸的是我不知道如何继续。

非常感谢任何帮助!

自己想出来了:解决方案

Just 添加 Failure 类型,因此 flatMap 的输入和输出 Failure 类型是相等的。或者换句话说:flatMap 无法将 Never 失败的 Just 转换为具有 Failure.

Publisher

在我的案例中缺少的行:

Just(payload)
  .setFailureType(to: APIError.self)

感谢

弄明白了

Just 需要增加 Failure,因为 flatMap 需要输入和输出流的 Failure 相同。

我们可以使用 setFailureType(to: <T>) 来做到这一点。我更新了我的问题以反映此解决方案。

你只是有一些编译时错误,Swift 类型推理系统无法查明它们发生在臭名昭著的古怪 flatMap Combine 运算符中。

  1. 首先,您在创建 Response 对象时使用了错误的参数顺序和 URLResponse 的类型。更正为:
return Response(
   response: response.response as! HTTPURLResponse,
   value: value
)
  1. 其次,您的 flatMap 实际上并不是 returning AnyPublisher<Data, Error> - 您在闭包中指定的 return 类型。 return 类型是 AnyPublisher<Response<T>, APIError>。所以,你可以改变它,但是你会 运行 进入另一个问题,即 flatMapError 类型必须与其上游相同,目前不是APIError,所以我建议将 mapError 移出 flatMap。它看起来像这样:
return Just(payload)
    .encode(encoder: JSONEncoder())
    .flatMap({ [weak self] payload -> AnyPublisher<Response<T>, Error> in
        guard let self = self else {
            return Fail(error: APIError.default("...")).eraseToAnyPublisher()
        }
        var request = URLRequest(url: url)
        request.httpMethod = "Methods.post"
        request.setValue(
            Mimetypes.json,
            forHTTPHeaderField: Headers.contentType
        )
        request.httpBody = payload
        return self.session
            .dataTaskPublisher(
                for: request
            )
            .tryMap { response -> Response<T> in
                let value = try self.decoder.decode(T.self, from: response.data)
                return Response(
                    response: response.response as! HTTPURLResponse,
                    value: value
                    )
            }
            .eraseToAnyPublisher()
            
    })
    .mapError { error in
        return APIError.default(error.localizedDescription)
    }
    .receive(on: DispatchQueue.main)
    .eraseToAnyPublisher()