如何在Combine框架中将
URLSession.DataTaskPublisher
转换为Future
。
在我看来,Future 发布者在这里更合适,因为调用只能发出一个响应并最终失败。
在 RxSwift 中有像
asSingle
这样的辅助方法。
我已经使用以下方法实现了这种转变,但不知道这是否是最好的方法。
return Future<ResponseType, Error>.init { (observer) in
self.urlSession.dataTaskPublisher(for: urlRequest)
.tryMap { (object) -> Data in
//......
}
.receive(on: RunLoop.main)
.sink(receiveCompletion: { (completion) in
if case let .failure(error) = completion {
observer(.failure(error))
}
}) { (response) in
observer(.success(response))
}.store(in: &self.cancellable)
}
}
有什么简单的方法可以做到这一点吗?
据我了解,在 RxSwift 中使用
.asSingle
的原因是,当您订阅时,您的订阅者会收到一个 SingleEvent
,它可以是 .success(value)
或 .error(error)
。因此,您的订阅者不必担心收到 .completion
类型的事件,因为没有这样的事件。
Combine 中没有类似的功能。在Combine中,从订阅者的角度来看,
Future
只是另一种类型的Publisher
,它可以发出输出值和.finished
或.failure(error)
。类型系统并不强制执行以下事实:Future
永远不会发出 .finished
。
因此,没有任何编程理由专门返回
Future
。您可能会争辩说,返回 Future
记录了您始终返回恰好一个输出或失败的意图。但这不会改变您编写订阅者的方式。
此外,由于Combine大量使用泛型,一旦你想将任何运算符应用于
Future
,你就不再有未来了。如果将 map
应用于某些 Future<V, E>
,您会得到 Map<Future<V, E>, V2>
,对于其他所有运算符也类似。这些类型很快就会失控,并掩盖了底部有一个 Future
的事实。
如果您确实愿意,您可以实现自己的运算符将任何
Publisher
转换为 Future
。但是您必须决定如果上游发出 .finished
该怎么办,因为 Future
无法发出 .finished
。
extension Publisher {
func asFuture() -> Future<Output, Failure> {
return Future { promise in
var ticket: AnyCancellable?
ticket = self.sink(
receiveCompletion: {
ticket?.cancel()
ticket = nil
switch $0 {
case .failure(let error):
promise(.failure(error))
case .finished:
// WHAT DO WE DO HERE???
fatalError()
}
},
receiveValue: {
ticket?.cancel()
ticket = nil
promise(.success($0))
})
}
}
}
不要将数据任务 publisher 转换为 Future,而是将 data 任务 转换为 Future。只需将 Future 包裹在对 URLSession 的
dataTask(...){...}.resume()
的调用中,问题就解决了。这正是未来的目的:将任何异步操作转变为发布者。
您需要将现有的发布者转换为
AnyPublisher<Value, Error>
,而不是尝试从函数返回“未来”。您可以使用 .eraseToAnyPublisher()
运算符来完成此操作。
func getUser() -> AnyPublisher<User, Error> {
URLSession.shared.dataTaskPublisher(for: request)
.tryMap { output -> Data in
// handle response
return output.data
}
.decode(type: User.self, decoder: JSONDecoder())
.mapError { error in
// handle error
return error
}
.eraseToAnyPublisher()
}