无法通过 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 运算符中。
- 首先,您在创建
Response
对象时使用了错误的参数顺序和 URLResponse
的类型。更正为:
return Response(
response: response.response as! HTTPURLResponse,
value: value
)
- 其次,您的
flatMap
实际上并不是 returning AnyPublisher<Data, Error>
- 您在闭包中指定的 return 类型。 return 类型是 AnyPublisher<Response<T>, APIError>
。所以,你可以改变它,但是你会 运行 进入另一个问题,即 flatMap
的 Error
类型必须与其上游相同,目前不是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()
我是 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 运算符中。
- 首先,您在创建
Response
对象时使用了错误的参数顺序和URLResponse
的类型。更正为:
return Response(
response: response.response as! HTTPURLResponse,
value: value
)
- 其次,您的
flatMap
实际上并不是 returningAnyPublisher<Data, Error>
- 您在闭包中指定的 return 类型。 return 类型是AnyPublisher<Response<T>, APIError>
。所以,你可以改变它,但是你会 运行 进入另一个问题,即flatMap
的Error
类型必须与其上游相同,目前不是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()