Skip to content

Instantly share code, notes, and snippets.

@saroar
Forked from dry1lud/combine-retry.md
Created September 1, 2020 19:23
Show Gist options
  • Save saroar/bde5266b5dd322a626c8545afc56bfec to your computer and use it in GitHub Desktop.
Save saroar/bde5266b5dd322a626c8545afc56bfec 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)")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment