Skip to content

Instantly share code, notes, and snippets.

@dry1lud
Last active November 27, 2023 10:07
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dry1lud/6cbc2642f94dd7a1ed7a4ed63223c0e5 to your computer and use it in GitHub Desktop.
Save dry1lud/6cbc2642f94dd7a1ed7a4ed63223c0e5 to your computer and use it in GitHub Desktop.
Retry operation in Swift/Combine.

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)")
@augustin-u
Copy link

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?

@PankajGaikar
Copy link

@GizmoJon were you able to get solve 401 refresh token?

@rahulGraphy
Copy link

@GizmoJon, @PankajGaikar were you guys able to crack this 401 refresh token problem? I am also stuck with the same problem, I would really appreciate it if you guys can share your knowledge with me on this.

@augustin-u
Copy link

augustin-u commented May 16, 2021

@GizmoJon, @PankajGaikar were you guys able to crack this 401 refresh token problem? I am also stuck with the same problem, I would really appreciate it if you guys can share your knowledge with me on this.

Hey there! Please check my Combine Networking App demo repo. That might give you an idea!

@KompoD
Copy link

KompoD commented Nov 27, 2023

@GizmoJon, @PankajGaikar were you guys able to crack this 401 refresh token problem? I am also stuck with the same problem, I would really appreciate it if you guys can share your knowledge with me on this.

Hey there! Please check my Combine Networking App demo repo. That might give you an idea!

Hello! Pls give a link to your example repository

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment