SwiftRetrier v2

🪨 坚如磐石、简洁而全面的库,用于重试和重复 async throws 任务。

Swift 6 模式和 MainActor 友好 🥳

从 v0 或 v1 迁移?

一个拥有所有选项的重试器 ❄️

var conditionPublisher: AnyPublisher<Bool, Never>

// Fully configurable policy with good defaults. Also available: withConstantDelay(), withNoDelay()
let retrier = withExponentialBackoff() 
    // Fetch only when you've got network and your user is authenticated for example
    .onlyWhen(conditionPublisher)
    // Ensure your retrier gives up on some conditions
    .giveUpAfter(maxAttempts: 10)
    .giveUpAfter(timeout: 30)
    .giveUpOnErrors {
        $0 is MyFatalError
    }

指数退避与全抖动是默认且推荐的从后端获取数据的算法。

任务重试器和重复器 ❄️

您可以直接链式调用 job { try await job() } 来创建一个冷的任务重试器,您也可以重用任何重试器来创建多个任务重试器。

let fetcher = retrier.job { 
    try await fetchSomething() 
}

let poller = retrier
    // If you want to poll, well you can
    .repeating(withDelay: 30)
    .job { 
        try await fetchSomethingElse() 
    }

一旦设置了任务,您可以向您的(仍然是冷的 ❄️)重试器添加事件处理程序。

let fetcherWithEventHandler = fetcher.handleRetrierEvents {
    switch $0 {
        case .attemptSuccess(let value):
            print("Fetched something: \(value)")
        case .attemptFailure(let failure):
            print("An attempt #\(failure.index) failed with \(failure.error)")
        case .completion(let error):
            print("Fetcher completed with \(error?.localizedDescription ?? "no error")")
    }
}.handleRetrierEvents {
   // Do something fun 🤡
}

收集 🔥

所有任务重试器都是冷的发布者,并且

一旦进入 Combine 的世界,您就会知道该怎么做(否则请查看下一段)。

let cancellable = fetcher
   .sink { event in
        switch $0 {
            case .attemptSuccess(let value):
                print("Fetched something: \(value)")
            case .attemptFailure(let failure):
                print("An attempt #\(failure.index) failed with \(failure.error)")
            case .completion(let error):
                print("Poller completed with \(error?.localizedDescription ?? "no error")")
        }
   }

let cancellable = fetcher
    // Retrieve success values
   .success()
   .sink { fetchedValue in
      // Do something with values
   }

在并发上下文中等待值 🔥

如果您不重复,您可以在并发上下文中等待单个值,并且

// This will throw if you cancel the retrier or if any `giveUp*()` function matches
let value = try await withExponentialBackoff() 
    .onlyWhen(conditionPublisher)
    .giveUpAfter(maxAttempts: 10)
    .giveUpAfter(timeout: 30)
    .giveUpOnErrors {
        $0 is MyFatalError
    }
    .job {
        try await api.fetchValue()
    }
    .value

重试器契约

重试策略

重要的是要理解,策略不用于成功后重复,而仅用于失败后重试。 在重复时,策略在每次成功后从头开始重用。

内置的重试策略

ExponentialBackoffRetryPolicy 是根据最先进的算法实现的。查看可用的参数,您会识别出标准参数和选项。 您尤其可以选择 nonefull(默认)和 decorrelated 之间的抖动类型。

ConstantDelayRetryPolicy 按照您的期望执行,只是等待固定的时间量。

您可以使用 giveUp*() 函数添加失败条件。

自制策略

您可以创建自己的符合 RetryPolicy 的策略,它们将受益于相同的修饰符。 查看 ConstantDelayRetryPolicy.swift 以获取基本示例。

⚠️策略应该是无状态的。为了确保这一点,我建议使用 struct 类型来实现它们。

如果策略需要了解尝试历史记录,请确保在实现 policyAfter(attemptFailure:, delay:) -> any RetryPolicy 时传播所需的内容。

使用您的策略创建 DSL 入口点

public func withMyOwnPolicy() -> Retrier {
    let policy = MyOwnPolicy()
    return Retrier(policy: policy, conditionPublisher: nil)
}

背压

仍然会正确管理背压:如果没有需求,则不会向订阅者发送任何事件,并且在订阅者提供积极需求之前,不会尝试执行任务。

在实践中,除非您实现自己的 Subscriber,否则您不应该关心这一点。

从 v0 或 v1 迁移

我相信这些变化是最好的,并且 API 在未来不应该有太大变化。

贡献

欢迎使用 GitHub Issues 提出任何意见、批评、错误报告或功能请求。您也可以直接给我发送电子邮件至 pierre 奇怪的带有长圆形尾巴的 "a" pittscraft.com

许可

SwiftRetrier 在 MIT 许可下可用。 有关更多信息,请参见 LICENSE 文件。