将 URLSession.DataTaskPublisher 转换为未来发布者
Convert URLSession.DataTaskPublisher to Future publisher
如何在 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)
}
}
有什么简单的方法可以做到这一点吗?
不是将数据任务 publisher 转换为 Future,而是将 data task 转换为 Future。只需将 Future 包裹在对 URLSession 的 dataTask(...){...}.resume()
的调用周围,问题就解决了。这正是未来的目的:将任何异步操作转变为发布者。
据我了解,在 RxSwift 中使用 .asSingle
的原因是,当您订阅时,您的订阅者会收到一个 SingleEvent
,它是 .success(value)
或 .error(error)
。因此,您的订阅者不必担心收到 .completion
类型的事件,因为根本没有。
在 Combine 中没有等效项。在 Combine 中,从订阅者的角度来看,Future
只是另一种 Publisher
,它可以发出输出值和 .finished
或 .failure(error)
。类型系统不强制 Future
永远不会发出 .finished
.
因此,没有编程理由专门 return a Future
。您可能会争辩说 returning a Future
记录了您总是 return 只输出一个输出或失败的意图。但它不会改变您编写订阅者的方式。
此外,由于 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? = nil
ticket = self.sink(
receiveCompletion: {
ticket?.cancel()
ticket = nil
switch [=10=] {
case .failure(let error):
promise(.failure(error))
case .finished:
// WHAT DO WE DO HERE???
fatalError()
}
},
receiveValue: {
ticket?.cancel()
ticket = nil
promise(.success([=10=]))
})
}
}
}
如何return 函数的未来
您需要将现有发布者转换为 AnyPublisher<Value, Error>
,而不是尝试从函数 return 'future'。您可以使用 .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()
}
如何在 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)
}
}
有什么简单的方法可以做到这一点吗?
不是将数据任务 publisher 转换为 Future,而是将 data task 转换为 Future。只需将 Future 包裹在对 URLSession 的 dataTask(...){...}.resume()
的调用周围,问题就解决了。这正是未来的目的:将任何异步操作转变为发布者。
据我了解,在 RxSwift 中使用 .asSingle
的原因是,当您订阅时,您的订阅者会收到一个 SingleEvent
,它是 .success(value)
或 .error(error)
。因此,您的订阅者不必担心收到 .completion
类型的事件,因为根本没有。
在 Combine 中没有等效项。在 Combine 中,从订阅者的角度来看,Future
只是另一种 Publisher
,它可以发出输出值和 .finished
或 .failure(error)
。类型系统不强制 Future
永远不会发出 .finished
.
因此,没有编程理由专门 return a Future
。您可能会争辩说 returning a Future
记录了您总是 return 只输出一个输出或失败的意图。但它不会改变您编写订阅者的方式。
此外,由于 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? = nil
ticket = self.sink(
receiveCompletion: {
ticket?.cancel()
ticket = nil
switch [=10=] {
case .failure(let error):
promise(.failure(error))
case .finished:
// WHAT DO WE DO HERE???
fatalError()
}
},
receiveValue: {
ticket?.cancel()
ticket = nil
promise(.success([=10=]))
})
}
}
}
如何return 函数的未来
您需要将现有发布者转换为 AnyPublisher<Value, Error>
,而不是尝试从函数 return 'future'。您可以使用 .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()
}