Created
          August 11, 2022 21:18 
        
      - 
      
- 
        Save BigZaphod/50ccb254a6e7047ff5693a4381a3be37 to your computer and use it in GitHub Desktop. 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | // | |
| // Created by Sean Heber on 8/11/22. | |
| // | |
| import Foundation | |
| enum ExponentialBackoffError : Error { | |
| case retryLimitExceeded | |
| } | |
| /// Runs the `operation` until it succeeds. | |
| /// Success here is defined as not throwing, so if `operation` throws any errors, this function swallows them, waits a bit, and runs the `operation` again until either we reach the maximum attempts allowed or the task is cancelled. | |
| func retryWithExponentialBackoff<Result>(base: Double = 0.25, maxInterval: Double = 60, maxAttempts: Int? = nil, operation: () async throws -> Result) async throws -> Result { | |
| var attempt = 0 | |
| while maxAttempts == nil || attempt < maxAttempts! { | |
| try Task.checkCancellation() | |
| do { | |
| return try await operation() | |
| } catch { | |
| // errors from the operation are ignored! | |
| } | |
| // This uses an exponential backoff with jitter (hence the randomness). | |
| let sleep = base * Double(pow(Double(2), Double(attempt))) | |
| let seconds = Double.random(in: 0...min(maxInterval, sleep)) | |
| try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) | |
| attempt += 1 | |
| } | |
| throw ExponentialBackoffError.retryLimitExceeded | |
| } | 
And of course the onFailure function could also be made async there, too, and who knows what crazy possibilities that might unlock.
For clarity I would rename the onFaliure to shouldRetryOn: (Error, Int) -> Bool = { _,_ in true}  it will make it more explicitly a condition/filter like closure
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
            
Here's a variation on this idea that uses a function to decide if the thrown error is something we care about or not. If
onFailurethrows, then that'll cancel the retrying - otherwise it keeps going. This way you can implement your own maxAttempts logic, log the errors, or decide if certain errors are actually failures while ignoring and retrying for others.