在现代应用开发中,网络请求失败是常见问题。Riverpod 引入了自动重试(Automatic Retry)机制,旨在让应用在面对暂时性错误(如网络波动)时更具弹性。
本文将系统地介绍 Riverpod 的自动重试行为、默认策略、以及如何通过 riverpod_generator 自定义或禁用该功能。
1. 核心概念:什么是自动重试?
当一个 Provider 在计算过程中抛出异常(例如 HTTP 请求失败)时,Riverpod 会自动尝试重新执行该 Provider 的构建逻辑,直到成功或达到最大重试次数。
这种机制可以显著简化错误处理代码,开发者无需手动编写复杂的递归重试逻辑。
2. 默认重试策略
Riverpod 提供了一个智能的默认重试策略,设计目标是既能恢复暂时性错误,又避免因频繁重试而导致资源浪费。
- 重试次数:默认最多重试 10 次。
- 退避策略(Backoff):采用指数退避算法。
- 初次重试延迟:200ms。
- 后续延迟:每次翻倍(200ms -> 400ms -> 800ms ...)。
- 最大延迟:6.4 秒。
注意:
- Error 类型不重试:如
OutOfMemoryError或代码逻辑错误(Bug),这些被认为是不可恢复的,重试只会污染日志。 - ProviderException 不重试:如果当前 Provider 失败是因为它依赖的上游 Provider 失败了(抛出了
ProviderException),则不会重试当前 Provider,而是应该重试那个上游 Provider。
3. 使用 riverpod_generator 自定义重试逻辑
虽然默认行为已经足够应对大多数场景,但你可能需要根据业务需求调整重试逻辑(例如减少重试次数,或在特定错误下停止重试)。
我们可以通过 @Riverpod 注解中的 retry 参数来实现。
3.1 定义自定义重试函数
重试逻辑是一个简单的函数,接收当前重试次数和错误对象,返回下一次重试前的等待时间(Duration?)。如果返回 null,则停止重试。
import 'package:riverpod_annotation/riverpod_annotation.dart';
// 自定义重试策略示例
Duration? myRetryPolicy(int retryCount, Object error) {
// 1. 如果重试超过 3 次,停止重试
if (retryCount >= 3) return null;
// 2. 可以在这里判断 error 类型,例如如果是 "用户未授权" 错误,则不重试
// if (error is UnauthorizedException) return null;
// 3. 返回指数退避时间:200ms, 400ms, 800ms...
return Duration(milliseconds: 200 * (1 << retryCount));
}
3.2 在 Provider 中应用
使用 riverpod_generator 时,直接将上述函数传递给注解即可。
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:dio/dio.dart';
part 'data_provider.g.dart';
// 将自定义策略应用到 Provider
@Riverpod(retry: myRetryPolicy)
Future<String> fetchData(FetchDataRef ref) async {
// 模拟网络请求
final response = await Dio().get('https://api.example.com/data');
return response.data as String;
}
4. 全局配置与禁用
除了针对单个 Provider 设置,你也可以在应用的根节点对所有 Provider 生效,或者完全禁用重试。
4.1 全局设置 (在 ProviderScope 中)
如果你希望整个应用使用统一的重试策略,可以在 ProviderScope 中配置:
void main() {
runApp(
ProviderScope(
// 对所有未显式指定 retry 的 Provider 生效
retry: myRetryPolicy,
child: const MyApp(),
),
);
}
4.2 禁用重试
如果你希望某个 Provider 失败后立即报错,不进行任何重试,可以让重试函数始终返回 null。
禁用单个 Provider:
Duration? noRetry(int retryCount, Object error) => null;
@Riverpod(retry: noRetry)
Future<int> noRetryProvider(NoRetryProviderRef ref) async {
throw Exception('Fail immediately');
}
全局禁用:
ProviderScope(
retry: (retryCount, error) => null, // 全局禁用自动重试
child: const MyApp(),
);
5. 进阶行为:Future 的等待机制
在使用 ref.watch(provider.future) 监听异步 Provider 时,Riverpod 的自动重试机制会影响 await 的行为。
- 挂起等待:当 Provider 失败并开始重试时,
await provider.future不会立即抛出异常。 - 持续挂起:它会一直处于挂起状态(Pending),直到:
- 某次重试成功(返回数据)。
- 重试次数耗尽或策略返回
null(此时才抛出异常)。
这意味着在 UI 中使用 FutureBuilder 或 await 时,即使中间发生了一两次网络闪断,只要重试最终成功,用户端可能根本感知不到错误的发生。
总结
| 特性 | 说明 |
|---|---|
| 默认行为 | 自动重试,指数退避(200ms -> 6.4s),最多 10 次。 |
| 不可重试项 | Error (代码 Bug) 和 ProviderException (上游依赖错误)。 |
| 自定义方式 | 通过 @Riverpod(retry: myPolicy) 指定。 |
| generator 用法 | 传入一个返回 Duration? 的函数。 |
| UI 表现 | future 会等待重试完成,提供无缝体验。 |