🪨 坚如磐石、简洁而全面的库,用于重试和重复 async throws
任务。
Swift 6 模式和 MainActor
友好 🥳
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
}
failure()
和 completion()
过滤器也可用.finished
,您可以 sink {}
而无需处理完成attemptFailure
、attemptSuccess
和 completion
事件会被物化并作为值发送。success()
、failure()
和 completion()
快捷方式。如果您不重复,您可以在并发上下文中等待单个值,并且
// 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
false
作为其最后一个值后结束try await retrier.value
上抛出,并且也会嵌入到 RetrierEvent.completion
中。DispatchQueue.main
上发出事件MainActor
友好conditionPublisher
中断然后恢复后,其策略将从头开始重用。因此,giveUpAfter(maxAttempts:)
和 giveUpAfter(timeout:)
检查应用于当前尝试,忽略之前的尝试。重要的是要理解,策略不用于成功后重复,而仅用于失败后重试。 在重复时,策略在每次成功后从头开始重用。
ExponentialBackoffRetryPolicy 是根据最先进的算法实现的。查看可用的参数,您会识别出标准参数和选项。 您尤其可以选择 none
、full
(默认)和 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)
}
sink(receiveCompletion:)
或 assign(to:)
运算符的无限需求仍然会正确管理背压:如果没有需求,则不会向订阅者发送任何事件,并且在订阅者提供积极需求之前,不会尝试执行任务。
在实践中,除非您实现自己的 Subscriber
,否则您不应该关心这一点。
execute()
-> job()
*因为* 此修饰符不再触发执行publisher()
,任务重试器/重复器 *就是* 发布者 *因为* 没有理由添加额外的步骤我相信这些变化是最好的,并且 API 在未来不应该有太大变化。
欢迎使用 GitHub Issues 提出任何意见、批评、错误报告或功能请求。您也可以直接给我发送电子邮件至 pierre
奇怪的带有长圆形尾巴的 "a" pittscraft.com
。
SwiftRetrier 在 MIT 许可下可用。 有关更多信息,请参见 LICENSE 文件。