There is a function .retry() in Combine that helps to retry a request. Although it is not enough just to call retry() to achieve retring logic. The retry() function does not do another request but it re-subscribes only to a publisher. To make another request the tryCatch() might be used. In the code below if the first call fails there are three attempts to retry (retry(3)):
import UIKit
import Combine
import PlaygroundSupport
enum CustomNetworkingError: Error {
case invalidServerResponse
}
let backgroundQueue: DispatchQueue = DispatchQueue(label: "backgroundQueue")
let backendURL = URL(string: "https://google1.com")!
func dataPublisher(for request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), URLError> {
Future<(data: Data, response: URLResponse), URLError> { promise in
print("Make a request")
backgroundQueue.asyncAfter(deadline: .now() + 1) {
promise(.failure(URLError(.notConnectedToInternet)))
}
}
.eraseToAnyPublisher()
}
func dataLoader(backendURL: URL) -> AnyPublisher<Data, Error> {
let request = URLRequest(url: backendURL)
print("DataLoader")
return dataPublisher(for: request)
// We get here when a request fails
.tryCatch { (error) -> AnyPublisher<(data: Data, response: URLResponse), URLError> in
print("Try to handle an error")
guard error.code == .notConnectedToInternet else {
throw error
}
print("Re-try a request")
return dataPublisher(for: request) // <-- This is a point where another request is made
}
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw CustomNetworkingError.invalidServerResponse
}
return data
}
.eraseToAnyPublisher()
}
let anyCancellable = dataLoader(backendURL: backendURL)
.retry(3) // <-- Literal constant regulates how many times we will end up in tryCatch() when a request fails
.subscribe(on: backgroundQueue)
.receive(on: RunLoop.main)
.sink(receiveCompletion: { (result) in
switch result {
case .finished: ()
case .failure(let error):
guard let error = error as? URLError else {
return
}
print(error)
}
PlaygroundPage.current.finishExecution()
}) { (data) in
print(data)
}
PlaygroundPage.current.needsIndefiniteExecution = true
The output of this code follows below:
DataLoader
Make a request
Try to handle an error
Re-try a request
Make a request
Try to handle an error (first attempt to retry)
Re-try a request
Make a request
Try to handle an error (second attempt to retry)
Re-try a request
Make a request
Try to handle an error (third attempt to retry)
Re-try a request
Make a request
URLError(_nsError: Error Domain=NSURLErrorDomain Code=-1009 "(null)")
Great piece of example.
Any example of how you could handle a 401 error where you would need to refresh the access token ? (Classic example)
How would you go about it?