Skip to content

Instantly share code, notes, and snippets.

@BigZaphod
Created August 11, 2022 21:18
Show Gist options
  • Save BigZaphod/50ccb254a6e7047ff5693a4381a3be37 to your computer and use it in GitHub Desktop.
Save BigZaphod/50ccb254a6e7047ff5693a4381a3be37 to your computer and use it in GitHub Desktop.
//
// 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
}
@shles
Copy link

shles commented Jun 17, 2025

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