使用 Swift 组合的 HTTP POST 请求

HTTP POST request using Swift Combine

我对 Combine 声明还很陌生 API。我正在尝试为 SwiftUI 应用程序实现通用网络层。对于所有接收数据的请求,我了解如何构建数据流。

我的问题是我有一些 HTTP POST 请求 returns 没有数据。只有一个 HTTP 200 成功。我不知道如何创建一个发布者来处理可能失败的解码,因为响应正文中可能没有数据。这是我尝试过的:

func postResource<Resource: Codable>(_ resource: Resource, to endpoint: Endpoint) -> AnyPublisher<Resource?, NetworkError> {
        return Just(resource)
            .subscribe(on: queue)
            .encode(encoder: JSONEncoder())
            .mapError { error -> NetworkError in
                return NetworkError.encoding(error)
            }
            .map { data -> URLRequest in
                return endpoint.makeRequest(with: data)
            }
            .tryMap { request -> Resource? in
                self.session.dataTaskPublisher(for: request)
                    .tryMap { data, response -> Data in
                        guard let httpUrlResponse = response as? HTTPURLResponse else { throw NetworkError.unknown }
                        guard (200 ... 299).contains(httpUrlResponse.statusCode) else { throw NetworkError.error(for: httpUrlResponse.statusCode) }
                        return data
                    }
                    .tryMap { data -> Resource? in
                        return try? JSONDecoder().decode(Resource.self, from: data)
                    }
            }
            .mapError({ error -> NetworkError in
                switch error {
                case is Swift.DecodingError:
                    return NetworkError.decoding(error)
                case let urlError as URLError:
                    return .urlError(urlError)
                case let error as NetworkError:
                    return error
                default:
                    return .unknown
                }
            })
            .eraseToAnyPublisher()
    }

编译器抱怨 tryMap 行出现以下错误: Declared closure result 'Publishers.TryMap<URLSession.DataTaskPublisher, Resource?>' is incompatible with contextual type 'Resource?'

有人有想法吗? 谢谢!

enum NetworkError: Error {
    case encoding(Error)
    case error(for: Int)
    case decoding(Error)
    case urlError(URLError)
    case unknown
}

func postResource<Resource: Codable>(_ resource: Resource, to endpoint: Endpoint) -> AnyPublisher<Resource?, NetworkError> {
    Just(resource)
        .subscribe(on: queue)
            .encode(encoder: JSONEncoder())
            .mapError { error -> NetworkError in
               NetworkError.encoding(error)
            }
            .map { data -> URLRequest in
               endpoint.makeRequest(with: data)
            }
            .flatMap { request in // the key thing is here you should you use flatMap instead of map
                URLSession.shared.dataTaskPublisher(for: request)
                    .tryMap { data, response -> Data in
                        guard let httpUrlResponse = response as? HTTPURLResponse else { throw NetworkError.unknown }
                        guard 200 ... 299 ~= httpUrlResponse.statusCode else { throw NetworkError.error(for: httpUrlResponse.statusCode) }
                        return data
                    }
                    .tryMap { data -> Resource? in
                        try? JSONDecoder().decode(Resource.self, from: data)
                    }
            }
            .mapError({ error -> NetworkError in
                switch error {
                case is Swift.DecodingError:
                    return NetworkError.decoding(error)
                case let urlError as URLError:
                    return .urlError(urlError)
                case let error as NetworkError:
                    return error
                default:
                    return .unknown
                }
            })
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }