名称: asyncredux-async-actions 描述: 为API调用、数据库操作和其他异步工作创建AsyncRedux(Flutter)异步操作。
AsyncRedux 异步操作
基本异步操作结构
当一个action的reduce()方法返回Future<AppState?>而不是AppState?时,它就变成了异步操作。用于数据库访问、API调用、文件操作或任何需要await的工作。
class FetchUser extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final user = await api.fetchUser();
return state.copy(user: user);
}
}
与传统Redux需要中间件不同,AsyncRedux简化了它:只需返回一个Future即可工作。
关键规则:每条路径都必须有await
如果action是异步的(返回Future)并改变状态(返回非空状态),框架要求所有执行路径至少包含一个await。如果实际上不等待任何东西,永远不要声明Future<AppState?>。
有效模式
// 简单的异步带await
Future<AppState?> reduce() async {
final data = await fetchData();
return state.copy(data: data);
}
// 使用微任务(最小的有效await)
Future<AppState?> reduce() async {
await microtask;
return state.copy(timestamp: DateTime.now());
}
// 条件语句 - 两条路径都有await
Future<AppState?> reduce() async {
if (state.needsRefresh) {
return await fetchAndUpdate();
}
else return await validateCurrent();
}
// 总是返回null
Future<AppState?> reduce() async {
if (state.needsRefresh) {
await fetchAndUpdate();
}
return null;
}
无效模式(会导致问题)
// 错误:根本没有await
Future<AppState?> reduce() async {
return state.copy(counter: state.counter + 1);
}
// 错误:只在某些路径有await
Future<AppState?> reduce() async {
if (condition) {
return await fetchData();
}
return state; // 这条路径没有await!
}
// 错误:调用异步函数但没有await
Future<AppState?> reduce() async {
someAsyncFunction(); // 没有被等待
return state;
}
使用assertUncompletedFuture()
对于具有多个代码路径的复杂reducer,在最终返回前添加assertUncompletedFuture()。这在开发时运行时捕获违规:
class ComplexAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
if (state.cacheValid) {
// 可能意外跳过await的复杂逻辑
return processCache();
}
final data = await fetchFromServer();
final processed = transform(data);
assertUncompletedFuture(); // 验证至少发生了一次await
return state.copy(data: processed);
}
}
异步操作期间的状态更改
state getter可能在每次await后更改,因为其他action可能在您等待时修改状态:
class AsyncAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
print(state.counter); // 例如,5
await someSlowOperation();
// state.counter现在可能不同了(例如,10)
// 如果另一个action在await期间修改了它
print(state.counter);
return state.copy(counter: state.counter + 1);
}
}
使用initialState进行比较
使用initialState访问action被分发时的状态(永远不会更改):
class SafeIncrement extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final originalCounter = initialState.counter;
await validateWithServer();
// 检查我们等待时状态是否更改
if (state.counter != originalCounter) {
// 状态被另一个action修改
return null; // 中止我们的更改
}
return state.copy(counter: state.counter + 1);
}
}
分发异步操作
即发即弃
当不需要等待完成时,使用dispatch():
context.dispatch(FetchUser());
// 立即返回,action在后台运行
等待完成
使用dispatchAndWait()来等待action的完成:
await context.dispatchAndWait(FetchUser());
// 仅在action完成且状态更改后继续
print('用户已加载: ${context.state.user.name}');
并行分发多个
// 分发所有,不等待
context.dispatchAll([FetchUser(), FetchSettings(), FetchNotifications()]);
// 分发所有并等待所有完成
await context.dispatchAndWaitAll([FetchUser(), FetchSettings()]);
显示加载状态
使用isWaiting()在异步操作运行时显示加载指示器:
Widget build(BuildContext context) {
if (context.isWaiting(FetchUser)) return CircularProgressIndicator();
else return Text('你好, ${context.state.user.name}');
}
错误处理
抛出UserException用于面向用户的错误:
class FetchUser extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final response = await api.fetchUser();
if (response.statusCode == 404)
throw UserException('用户未找到。');
if (response.statusCode != 200)
throw UserException('加载用户失败。请重试。');
return state.copy(user: response.data);
}
}
在widget中检查失败:
Widget build(BuildContext context) {
if (context.isFailed(FetchUser)) {
return Text('错误: ${context.exceptionFor(FetchUser)?.message}');
}
// ...
}
完整示例
// 带有适当错误处理的异步操作
class LoadProducts extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
try {
final products = await api.fetchProducts();
return state.copy(products: products, productsLoaded: true);
} catch (e) {
throw UserException('无法加载产品。请检查您的连接。');
}
}
}
// 显示所有三种状态的widget
Widget build(BuildContext context) {
// 加载状态
if (context.isWaiting(LoadProducts)) {
return Center(child: CircularProgressIndicator());
}
// 错误状态
if (context.isFailed(LoadProducts)) {
return Center(
child: Column(
children: [
Text(context.exceptionFor(LoadProducts)?.message ?? '错误'),
ElevatedButton(
onPressed: () => context.dispatch(LoadProducts()),
child: Text('重试'),
),
],
),
);
}
// 成功状态
return ListView.builder(
itemCount: context.state.products.length,
itemBuilder: (_, i) => ProductTile(context.state.products[i]),
);
}
返回类型警告
永远不要直接返回FutureOr<AppState?>。AsyncRedux必须知道action是同步还是异步:
// 正确
Future<AppState?> reduce() async { ... }
// 正确
AppState? reduce() { ... }
// 错误 - 抛出StoreException
FutureOr<AppState?> reduce() { ... }
参考
文档中的URL:
- https://asyncredux.com/flutter/basics/async-actions
- https://asyncredux.com/flutter/basics/actions-and-reducers
- https://asyncredux.com/flutter/advanced-actions/redux-action
- https://asyncredux.com/flutter/basics/failed-actions
- https://asyncredux.com/flutter/basics/dispatching-actions
- https://asyncredux.com/flutter/basics/wait-fail-succeed