I want to make a network request more than one time when some error occurs using retry() from Swift/Combine. The block inside the publisher is called once only which means one only one request is made for a real app when error happens. My code is:
import UIKit
import Combine
import PlaygroundSupport
enum TestFailureCondition: Error {
case invalidServerResponse
}
var backgroundQueue: DispatchQueue = DispatchQueue(label: "backgroundQueue")
var failPublisher: AnyPublisher<(Data, URLResponse), Error> {
Future<(Data, URLResponse), Error> { promise in
print("Attempt to call")
backgroundQueue.asyncAfter(deadline: .now() + Double.random(in: 1..<3)) {
promise(.failure(TestFailureCondition.invalidServerResponse))
}
}
.eraseToAnyPublisher()
}
let cancellable = failPublisher
.print("(1)>")
.retry(3)
.print("(2)>")
.sink(receiveCompletion: { fini in
print(" ** .sink() received the completion:", String(describing: fini))
PlaygroundPage.current.finishExecution()
}, receiveValue: { stringValue in
print(" ** .sink() received \(stringValue)")
})
PlaygroundPage.current.needsIndefiniteExecution = true
I expect that backgroundQueue.asyncAfter(deadline)
is called three time before some error happens. Does anyone know why?
CombineLatest publisher collects the first value from all three publishers and emits them as a single tuple. CombineLatest continues sending new values even when only one publisher emits a new value.
If you’ve been trying out SwiftUI, you’ve likely been using Combine quite a lot already. Types like ObservableObject and Property Wrappers like @Published all use Combine under the hood. It’s a powerful framework to dynamically respond to value changes over time. Build better software, faster.
As you can see, the MergeMany operator allows me to create a single pipe for cached and fresh data where the cached information usually appears first and then replaced by new data. To learn about building custom Combine operators, take a look at my “Building custom Combine operators in Swift” post.
The Combine framework comes with so-called Publishers and subscribers. If you’re familiar with RxSwift: Different namings, but they both give the same understanding. A Publisher exposes values that can change on which a subscriber subscribes to receive all those updates.
Future
runs its body once, as soon as it is created, even if nothing subscribes to it. It saves the result and completes all subscriptions with that same result. So using the retry
operator on a Future
won't make the Future
run its body again on failure.
You want each subscription to run the body again. You can do that by wrapping your Future
in a Deferred
. The Deferred
will create a new Future
for every subscription.
var failPublisher: AnyPublisher<(Data, URLResponse), Error> {
Deferred {
Future<(Data, URLResponse), Error> { promise in
print("Attempt to call")
backgroundQueue.asyncAfter(deadline: .now() + Double.random(in: 1..<3)) {
promise(.failure(TestFailureCondition.invalidServerResponse))
}
}
}
.eraseToAnyPublisher()
}
I managed to achieve the expected behaviour by utilising tryCatch()
function and making another request: Link
The link contains two ways to achieve the same behaviour including Deferred {}
mentioned above.
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