- 前端
【免费下载链接】fish-redux
An assembled flutter application framework.
导读
onError是 Fish Redux 为Component/Page提供的异常处理回调,用于集中捕获由 Effect 产生的业务异常——无论异常来自同步 Effect 还是异步 Effect,都会被统一送入同一个处理器。本文以 on-error-cn.md 为骨架,结合 Component 构造函数 的参数签名、Effect 派发链路 以及仓库测试示例,讲解onError的签名、语义、判定规则与真实用法,并顺带说明它与 View 层兜底中间件的分工边界,帮助你理解"站在更高抽象层面简化业务代码"这一设计意图。
一、为什么需要统一的 onError
在 Fish Redux 的组件模型中,业务逻辑被拆解为 Reducer(纯函数状态变更)与 Effect(副作用,如网络请求、本地 IO、事件回调)。Effect 中天然会产生异常:同步函数里throw出来的异常、异步函数(Future)中抛出的错误,都可能发生在业务代码的任意角落。
如果没有统一的异常处理机制,常见的做法是在每个 Effect 分支里各自try/catch:
if (action.type == Action.load) { try { final data = await api.fetch(); ctx.dispatch(ActionCreator.onData(data)); } catch (e) { // 每个分支都要自己处理一遍 showToast('load failed'); } }当页面分支一多,这段样板代码就会反复出现,且每个分支的容错策略很容易写得五花八门。Fish Redux 给出的答案是:在组件构造时提供一个onError回调,让框架统一捕获 Effect 产生的业务异常,集中决策"哪些异常需要提示用户、哪些异常可以被静默吸收",从而把异常策略从业务分支中抽离出来,站在更高抽象角度对业务代码做合理简化。
从源码结构看,Logic<T>(logic.dart)把组件逻辑封装为四部分——Reducer、Effect、Dependencies、Key,其中 Effect 的创建与派发由框架接管(createEffectDispatch、createNextDispatch、createDispatch,见 helper.dart),这为框架级统一捕获异常提供了天然的挂载点。
二、onError 的签名与语义
onError在Component<T>构造函数中以命名参数形式接收(component.dart),典型写法如下:
bool onMessageError(Exception e, Context<String> ctx) { if (e is BizException) { /// do some toast return true; } return false; } class MessageComponent extends Component<String> { MessageComponent() : super( view: buildMessageView, effect: buildEffect(), reducer: buildMessageReducer(), onError: onMessageError, ); }要点拆解:
| 要素 | 说明 |
|---|---|
| 回调签名 | bool Function(Exception e, Context<T> ctx) |
参数e | 被捕获到的异常对象,通常是 Effect 中抛出的具体异常实例 |
参数ctx | 抛出异常时所在的组件Context<T>,可用于读取ctx.state、ctx.dispatch继续下发 Action |
返回值bool | 中断标记:返回true表示该异常已被处理、拦截(interrupt);返回false表示异常未被本处理器处理,交由链路继续按默认规则放行 |
| 传入位置 | Component、Page构造函数的命名参数onError |
bool返回值的"中断(interrupt)"语义与 Fish Redux 的 Effect 约定一脉相承:在 basic.dart 中,Effect<T>的类型注释明确写着"Interrupt if not null not false"(返回非 null 且非 false 即视为中断),同步函数用bool表达中断,异步函数则用Future<void>表示"应始终被中断"。onError的true/false正是沿用同一套判定习惯——true意味着"我认识这个异常,已处理,不要再往下冒泡"。
从类型上还应注意:由于Component<T>的 State 泛型不同,Context<T>的类型参数会随组件变化。例如仓库测试示例中 ToDoList 页面的处理器签名是bool toDoListErrorHandler(Exception exception, Context<ToDoList> ctx)(test_widgets/lib/page/page.dart),与Context<String>的MessageComponent并不相同,编写时需与具体组件 State 类型保持一致。
三、捕获范围:同步 Effect 与异步 Effect
原文档强调onError"集中处理由 Effect 产生的业务异常,无论是同步函数还是异步函数"。这一点可以从 Effect 的派发实现中得到印证:
在 helper.dart 中,createEffectDispatch直接调用用户 Effect 并透传返回值:
Dispatch createEffectDispatch<T>(Effect<T> userEffect, Context<T> ctx) { return (Action action) { final Object result = userEffect?.call(action, ctx); // ... return result; }; }- 同步 Effect:
userEffect?.call(action, ctx)在派发线程内执行,throw的异常会沿调用栈被上层捕获,进入onError; - 异步 Effect:当 Effect 返回
Future(如toDoListEffectAsync用Future.delayed包一层后执行,见 test_widgets/lib/page/page.dart),异步任务中抛出的错误同样会被框架统一收口到onError。
也就是说,只要异常来源是 Effect(无论同步异步),组件无需感知异常发生的位置与时机,统一交给onError决策即可。
同步与异步的细微差别
根据 basic.dart 的注释约定,Effect 本身也区分同步/异步中断语义:
- 同步 Effect:返回
bool,true表示中断(后续不再执行 nextDispatch); - 异步 Effect:返回
Future<void>,语义上"应始终被中断"。
而onError回调始终是同步的bool函数:它在异常发生的当下被调用,返回true即吞掉该异常(例如只弹 toast),返回false则继续走框架默认的放行/兜底逻辑。测试示例 page_test.dart 中通过instrumentError<ToDoList>(toDoListErrorHandler, ...)包装onError来插桩断言异常被捕获,也印证了onError是 Effect 异常处理的统一入口。
四、配合分支异常的实战模式:已知异常与未知异常
仓库测试代码(test_widgets/lib/page/)给出了一个非常典型的实战模式:在 Effect 中按 Action 类型throw不同类型的异常,在onError中按异常类型分类处理。
Effect 侧(page.dart):
bool toDoListEffect(Action action, Context<ToDoList> ctx) { if (action.type == ToDoListAction.onKnowException) { throw KnowException(); // 已知异常:业务可识别 } else if (action.type == ToDoListAction.onUnKnowException) { throw UnKnowException(); // 未知异常:不认识的错误 } return false; }异常类型定义(exception.dart):
class KnowException implements Exception { // 自定义 == 便于按类型比较 } class UnKnowException implements Exception { // ... }onError 侧(page.dart):
bool toDoListErrorHandler(Exception exception, Context<ToDoList> ctx) { print('onErr:$exception'); if (exception is KnowException) { return true; // 已知异常:已处理(如 toast),拦截 } return false; // 未知异常:不处理,继续放行 }这种"Effect 只管throw,onError 统一分类"的写法,正是原文档所说"站在更高抽象角度对业务代码做合理简化"的直接体现:
- Effect 内无需任何
try/catch,专注业务主流程; - 已知异常(如
BizException、KnowException)在onError中集中提示用户并返回true拦截; - 未知异常返回
false放行,交由链路后续处理(或由上层中间件兜底),不会让未预期错误悄悄淹没。
五、onError 与 View 层兜底中间件的分工
需要澄清一个容易混淆的边界:onError只负责 Effect 产生的业务异常,View 层的渲染异常并不走它。仓库源码中另有一组safety*中间件用于 View/Adapter 渲染兜底:
safetyView(safety_view.dart):包装ViewBuilder,在 build 抛出异常时调用用户提供的onError(注意此处onError是中间件参数,签名带StackTrace、component、store),未提供回调时降级为Container(width: 0, height: 0);safetyAdapter(safety_adapter.dart):包装AdapterBuilder,区分"构建 ListAdapter 阶段"与"逐 item 构建阶段"两处 try/catch,异常时同样回调onError或返回空容器/空列表。
两个safety*中间件内部都有isDebug()判断:调试模式下直接放行原始逻辑(异常立即暴露),仅发布/非调试构建下才启用兜底。这与onError的定位形成互补:
| 异常来源 | 处理入口 | 典型场景 |
|---|---|---|
| Effect 同步/异步业务异常 | 组件onError回调 | 网络失败、业务校验失败、已知/未知异常分类 |
| View build 阶段异常 | safetyView中间件 | 渲染逻辑抛错,避免白屏 |
| Adapter item 构建异常 | safetyAdapter中间件 | 列表单项渲染异常,避免整表崩溃 |
从命名和传参看,两套机制互不干扰:组件的onError接收(Exception e, Context<T> ctx),而中间件的onError接收(dynamic e, StackTrace st, {component, store})并返回一个兜底Widget。设计目标是让"业务错误"与"渲染错误"各归其位。
六、接入方式与验证路径
1. 在 Component 中接入
如前文示例,在Component<T>构造函数中传入onError命名参数即可。Page<T, P>继承自Component<T>(page.dart),因此 Page 构造同样支持onError(构造参数透传见 page.dart)。当某个组件/页面没有自定义onError时,Effect 异常将不被拦截,沿派发链路按默认语义继续放行。
2. 在测试中验证
仓库的 ToDoList 测试工程把"点击 Error 按钮 → Effect 抛 KnowException → onError 捕获"串成一条可观察链路:view 中点击Error按钮派发onKnowException(page.dart),Effect 抛异常,toDoListErrorHandler打印onErr:$exception并返回true。虽然onError在实际用例中默认被注释(page.dart),但配合 page_test.dart 中被注释的instrumentError插桩写法,可以清晰还原"用包装函数包裹 onError 以断言异常是否被处理"的测试思路。
3. 边界与前提
onError的参数类型Context<T>与组件 State 泛型绑定,跨组件复用处理器时需注意泛型匹配;onError覆盖的是 Effect 异常,Reducer 与 View 的异常处理分别依赖中间件体系,不属于本机制职责范围;- 返回
true会拦截异常,请确保该分支确实完成了对用户可见的处理(如 toast、日志),避免"吞掉异常却不做任何反馈"。
总结
Fish Redux 的onError以极小的 API 面(一个bool回调)为 Effect 的同步/异步业务异常提供了统一收口:Effect 专注于"抛",onError 专注于"分类与决策",true拦截、false放行的中断语义与框架的 Effect 约定一脉相承。配合 helper.dart 的派发链路与 test_widgets/lib/page/page.dart 的已知/未知异常示例,你可以轻松把异常策略从每个业务分支中剥离出来,实现代码的集中简化。若需进一步了解 View/Adapter 渲染异常兜底,可继续阅读 safety_view.dart 与 safety_adapter.dart。
- 前端
【免费下载链接】fish-redux
An assembled flutter application framework.
相关推荐
fish-redux OnError 机制解析:统一处理 Effect 业务异常的抽象实践
fish redux OnError 机制解析:统一处理 Effect 业务异常的抽象实践 本篇技术指南聚焦 fish redux 框架中的 OnError 设
前端h3 错误处理完全指南:HTTPError、未处理异常与 onError 捕获机制
h3 错误处理完全指南:HTTPError、未处理异常与 onError 捕获机制 H3 会在 请求生命周期 https://link.gitcode.com/
后端Web框架Fish Redux 的 Effect 机制详解:副作用处理、异步请求与 Self-First-Broadcast 执行模型
Fish Redux 的 Effect 机制详解:副作用处理、异步请求与 Self First Broadcast 执行模型 本文围绕 Fish Redux 框
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考