name: asyncredux-retry-mixin description: 为动作失败添加具有指数退避的自动重试的 Retry 混入。涵盖单独使用 Retry 进行有限重试、结合 UnlimitedRetries 进行无限重试以及配置重试行为。
重试混入
Retry 混入使用指数退避自动重试失败的动作。当 reduce() 方法中发生错误时,动作会以逐渐增加的延迟重新执行。
基本用法
将 Retry 混入添加到任何应在失败时自动重试的动作中:
class LoadDataAction extends AppAction with Retry {
Future<AppState?> reduce() async {
var data = await fetchDataFromServer();
return state.copy(data: data);
}
}
使用此混入:
- 如果动作失败,它会自动重试最多 3 次(默认)
- 每次重试等待时间比前一次更长(指数退避)
- 如果所有重试都失败,则抛出原始错误
配置参数
覆盖这些 getter 以自定义重试行为:
class LoadDataAction extends AppAction with Retry {
// 首次重试前的延迟(默认:350ms)
int get initialDelay => 500;
// 延迟增长乘数(默认:2)
int get multiplier => 2;
// 最大重试尝试次数(默认:3)
int get maxRetries => 5;
// 延迟上限以防止过度等待(默认:5000ms)
int get maxDelay => 10000;
Future<AppState?> reduce() async {
var data = await fetchDataFromServer();
return state.copy(data: data);
}
}
| 参数 | 默认值 | 目的 |
|---|---|---|
initialDelay |
350 ms | 首次重试前的等待时间 |
multiplier |
2 | 尝试间延迟的增长因子 |
maxRetries |
3 | 最大重试次数(总执行次数 = maxRetries + 1) |
maxDelay |
5 秒 | 延迟上限以防止过度等待 |
重试序列示例
使用默认设置(initialDelay=350ms, multiplier=2, maxRetries=3):
- 初始尝试 - 动作运行,失败
- 等待 350ms - 首次重试,失败
- 等待 700ms - 第二次重试,失败
- 等待 1400ms - 第三次重试,失败
- 抛出错误 - 所有重试耗尽
时间考虑
重试延迟在 reducer 完成后开始,而不是从动作派发时开始。如果 reduce() 花费 1 秒失败且 initialDelay 为 350ms,首次重试在动作开始后 1.35 秒开始。
跟踪重试尝试
在动作中访问 attempts getter 以了解当前运行的尝试:
class LoadDataAction extends AppAction with Retry {
Future<AppState?> reduce() async {
print('尝试 ${attempts + 1}'); // 0 索引,所以首次尝试是 0
if (attempts > 0) {
// 重试时可能尝试不同的服务器
return state.copy(data: await fetchFromBackupServer());
}
return state.copy(data: await fetchFromPrimaryServer());
}
}
无限重试
将 UnlimitedRetries 与 Retry 结合以无限重试,直到动作成功:
class CriticalSyncAction extends AppAction with Retry, UnlimitedRetries {
Future<AppState?> reduce() async {
await syncCriticalData();
return state.copy(syncComplete: true);
}
}
这等同于将 maxRetries 设置为 -1。
警告: 将 await dispatchAndWait(action) 与 UnlimitedRetries 一起使用,如果动作继续失败,可能会无限期挂起。谨慎使用,并考虑动作是否有可能最终成功。
重要行为注意事项
仅 reduce() 失败触发重试
Retry 混入仅当 reduce() 方法中发生错误时重试。before() 方法中的失败不会触发重试 - 它们立即失败。
class LoadDataAction extends AppAction with Retry {
@override
Future<void> before() async {
// 这里的错误不会触发重试 - 动作立即失败
await validatePermissions();
}
Future<AppState?> reduce() async {
// 仅这里的错误触发重试机制
return state.copy(data: await fetchData());
}
}
动作变为异步
所有使用 Retry 混入的动作都变为异步,无论其原始同步性质如何。这是因为重试机制需要在尝试之间等待。
与非重入结合(最佳实践)
大多数使用 Retry 的动作还应包含 NonReentrant 混入以防止多个实例同时运行:
class SaveDataAction extends AppAction with NonReentrant, Retry {
Future<AppState?> reduce() async {
await saveToServer();
return state.copy(saved: true);
}
}
这防止以下场景:
- 用户多次点击“保存”
- 多个重试序列并行运行
- 服务器收到重复或冲突请求
与 CheckInternet 结合
对于网络操作,将 Retry 与 CheckInternet 结合以确保在尝试动作前有连接:
class FetchUserProfile extends AppAction with CheckInternet, Retry {
Future<AppState?> reduce() async {
var profile = await api.getUserProfile();
return state.copy(profile: profile);
}
}
CheckInternet 混入首先运行。如果没有连接,动作立即失败而不尝试重试。
常见用例
具有临时失败的 API 调用
class FetchProductsAction extends AppAction with Retry {
int get maxRetries => 3;
int get initialDelay => 500;
Future<AppState?> reduce() async {
var products = await api.getProducts();
return state.copy(products: products);
}
}
关键同步操作
class SyncPendingChanges extends AppAction with Retry, UnlimitedRetries {
int get initialDelay => 1000;
int get maxDelay => 30000; // 重试间延迟上限为 30 秒
Future<AppState?> reduce() async {
await syncService.pushPendingChanges();
return state.copy(hasPendingChanges: false);
}
}
具有扩展重试的支付处理
class ProcessPaymentAction extends AppAction with NonReentrant, Retry {
final double amount;
ProcessPaymentAction(this.amount);
int get maxRetries => 5;
int get initialDelay => 1000;
int get multiplier => 2;
int get maxDelay => 10000;
Future<AppState?> reduce() async {
var result = await paymentGateway.process(amount);
return state.copy(paymentStatus: result.status);
}
}
混入兼容性
兼容:
CheckInternetNoDialogAbortWhenNoInternetNonReentrantThrottleDebounce
可结合:
UnlimitedRetries(启用无限重试)
完整示例与所有选项
class RobustApiAction extends AppAction
with CheckInternet, NonReentrant, Retry {
// 重试配置
int get initialDelay => 500; // 首次重试前 500ms
int get multiplier => 2; // 每次延迟加倍
int get maxRetries => 4; // 最多尝试 5 次(总次数)
int get maxDelay => 8000; // 等待时间不超过 8 秒
Future<AppState?> reduce() async {
if (attempts > 0) {
print('重试尝试 $attempts');
}
var data = await api.fetchCriticalData();
return state.copy(data: data);
}
}
参考
文档中的 URL:
- https://asyncredux.com/sitemap.xml
- https://asyncredux.com/flutter/advanced-actions/action-mixins
- https://asyncredux.com/flutter/advanced-actions/control-mixins
- https://asyncredux.com/flutter/advanced-actions/wrapping-the-reducer
- https://asyncredux.com/flutter/advanced-actions/errors-thrown-by-actions
- https://asyncredux.com/flutter/basics/async-actions
- https://asyncredux.com/flutter/basics/failed-actions
- https://asyncredux.com/flutter/basics/wait-fail-succeed