How to convert URLSession.DataTaskPublisher
to Future
in Combine framework.
In my opinion, the Future publisher is more appropriate here because the call can emit only one response and fails eventually.
In RxSwift there is helper method like asSingle
.
I have achieved this transformation using the following approach but have no idea if this is the best method.
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)
}
}
Is there any easy way to do this?
As I understand it, the reason to use .asSingle
in RxSwift is that, when you subscribe, your subscriber receives a SingleEvent
which is either a .success(value)
or a .error(error)
. So your subscriber doesn't have to worry about receiving a .completion
type of event, because there isn't one.
There is no equivalent to that in Combine. In Combine, from the subscriber's point of view, Future
is just another sort of Publisher
which can emit output values and a .finished
or a .failure(error)
. The type system doesn't enforce the fact that a Future
never emits a .finished
.
Because of this, there's no programmatic reason to return a Future
specifically. You could argue that returning a Future
documents your intent to always return either exactly one output, or a failure. But it doesn't change the way you write your subscriber.
Furthermore, because of Combine's heavy use of generics, as soon as you want to apply any operator to a Future
, you don't have a future anymore. If you apply map
to some Future<V, E>
, you get a Map<Future<V, E>, V2>
, and similar for every other operator. The types quickly get out of hand and obscure the fact that there's a Future
at the bottom.
If you really want to, you can implement your own operator to convert any Publisher
to a Future
. But you'll have to decide what to do if the upstream emits .finished
, since a Future
cannot emit .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 $0 {
case .failure(let error):
promise(.failure(error))
case .finished:
// WHAT DO WE DO HERE???
fatalError()
}
},
receiveValue: {
ticket?.cancel()
ticket = nil
promise(.success($0))
})
}
}
}
Instead of converting a data task publisher into a Future, convert a data task into a Future. Just wrap a Future around a call to a URLSession's dataTask(...){...}.resume()
and the problem is solved. This is exactly what a future is for: to turn any asynchronous operation into a publisher.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With