前端

Odoo 前端报错为什么不是“控制台红了就算完”:error service、处理器注册表与兜底弹窗链路讲透

很多系统对前端异常的态度还停留在“浏览器会报错、开发者自己看 console”。但 Odoo 的 `error_service.js` 和 `error_handlers.js` 说明,成熟 Web Client 会把异常统一包装、分流、降噪、弹窗化,再根据用户身份决定到底展示多少信息。它处理的不是“报不报错”,而是错误如何进入产品级运行时。

前端
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多前端项目都有一个默认前提:

  • 浏览器会报错;
  • 开发者打开 console 看;
  • 真有问题再自己排。

这在小项目里还能勉强工作,但在 Odoo 这种大型 Web Client 里,完全不够。

因为真实世界里的前端错误,不只一种:

  • 普通 JS 异常;
  • Promise 未处理拒绝;
  • RPC 返回的服务端异常;
  • 断网导致的连接错误;
  • 第三方脚本跨域报错;
  • 浏览器扩展乱入;
  • 访客用户根本不该看到技术细节。

所以 Odoo 的前端错误系统要解决的,根本不是“控制台有没有红字”,而是:

异常进入前端运行时之后,怎样先分类、再分流、最后决定给谁看什么。

一、error_service 先把浏览器原始异常统一包装

error_service.js 会监听两类核心入口:

  • window.error
  • window.unhandledrejection

然后把原始错误整理成几类统一对象:

  • UncaughtClientError
  • UncaughtPromiseError
  • ThirdPartyScriptError
  • 以及特殊的 HTMLElementLoadingError

这一步很关键。

因为浏览器原生错误来源太杂,字段也不稳定。如果不先做统一包装,后面的处理器几乎没法建立稳定协议。

Odoo 的做法等于先把“浏览器事件”翻译成“框架可理解的错误对象”。

二、真正的主角不是某个 try/catch,而是处理器注册表

handleError() 里最值得学的一段,是它会遍历:

  • registry.category("error_handlers").getEntries()

也就是说,Odoo 不是把所有错误逻辑硬写死在一个文件里,而是交给一个可扩展的处理器链。

这非常像中间件思路:

  • 每个 handler 判断自己能不能处理;
  • 能处理就接管;
  • 不能处理就交给下一个;
  • 全部都不接,再走兜底。

这让错误处理不再是“一个巨大 if/else 墓地”,而是一条可插拔、可排序、可扩展的管线。

三、为什么 cause 要一路往里钻

源码里会不断沿着 error.cause 往下找到最原始的 originalError

这一步经常被忽略,但非常重要。

因为很多框架级错误,外面包了好几层壳:

  • 最外层是 Promise 未处理;
  • 里面可能是 RPCError;
  • 再里面才是真正业务异常。

如果只看最外层,你只能知道“Promise 爆了”;继续向下找,才知道:

  • 是连接断了;
  • 是后端抛了业务异常;
  • 还是第三方脚本在捣乱。

成熟错误系统,看的从来不是外壳,而是根因。

四、RPC 错误为什么要单独接管

error_handlers.js 里,rpcErrorHandler 优先判断:

  • 这是不是 UncaughtPromiseError
  • 里面包的是不是 RPCError

如果是,处理逻辑就明显升级了:

  • preventDefault(),避免浏览器按普通未处理拒绝乱报;
  • 再看 exceptionNameexception_class
  • 优先尝试命中 error_notificationserror_dialogs 注册表;
  • 最后才兜底到 RPCErrorDialog

这套设计特别像后端里的异常映射:

  • 某些异常只该给提示气泡;
  • 某些异常要专门弹一个业务化对话框;
  • 再不行才退回通用错误弹窗。

也就是说,Odoo 没把 RPC 错误当成“普通 JS 爆了”,而是承认:

这本质上是一类产品语义更强的异常。

五、断网不是“报错一下”,而是一条恢复链路

lostConnectionHandler 也很精彩。

当捕获到 ConnectionLostError 时,它不会只弹一句“网络错误”。而是:

  • 先弹一个 sticky 通知;
  • 再轮询 /web/webclient/version_info
  • 使用指数退避 + 随机抖动;
  • 恢复后主动提示“你已经重新在线”。

这背后体现的不是报错技巧,而是产品意识。

因为断网对用户来说不是“一个异常对象”,而是一段状态:

  • 现在离线;
  • 正在重连;
  • 已恢复。

Odoo 把它当状态机处理,而不是当一次性报错处理。

六、默认处理器负责兜底,但它并不是第一个出手的人

defaultHandler 的 sequence 是 100,排在更后面。

它做的事情反而很简单:

  • 根据错误类型选择 ClientErrorDialogNetworkErrorDialog 或通用 ErrorDialog
  • 再把 traceback、message、name 等信息交给弹窗。

这恰好说明兜底处理器的角色:

  • 它不需要最懂业务;
  • 它只要保证“前面都没接住时,系统仍然给出统一且可控的反馈”。

所以稳定系统不怕有兜底,怕的是没有兜底。

七、对访客吞错,不是掩耳盗铃,而是权限边界

很容易被忽略的一段,是 swallowAllVisitorErrors

  • 如果不是内部用户;
  • 又不是 debug 或 test 模式;
  • 直接返回 true,吞掉错误。

很多人一看到“吞错”就本能反感,但这里恰恰是合理的。

因为公开前台访客看到技术 traceback 往往只有坏处:

  • 带来恐慌;
  • 暴露实现细节;
  • 对解决问题没有帮助;
  • 还可能影响品牌感受。

换句话说,错误信息不是越透明越好,而是要按角色分层。

内部用户需要足够信息定位问题;访客用户只需要界面别被技术细节砸脸。

八、Odoo 甚至主动给某些“噪声错误”降噪

比如 ResizeObserver loop completed with undelivered notifications. 这类浏览器噪声,源码会直接 preventDefault() 并忽略。

还有第三方脚本跨域导致的 redacted error、某些浏览器扩展乱扔的异常,也会根据 debug 状态决定是否继续展示。

这点很现实。

因为大型前端系统的目标不是“把所有噪声都喊出来”,而是:

  • 真问题别漏;
  • 假信号别淹没真正报警。

这和监控系统里的降噪思路完全一致。

九、二开时最该学什么

不是去复制每个类名,而是学这条分层链路:

  1. 统一入口:浏览器原始错误先标准化;
  2. 分类对象:客户端、Promise、第三方脚本、RPC 各自归类;
  3. 处理器注册表:按 sequence 分流;
  4. 角色分层:内部用户与访客看到不同层次的信息;
  5. 统一兜底:没人接住时仍然可控。

只要把这几层搭起来,错误系统才算从“开发者工具附件”升级成“产品基础设施”。

十、总结:错误处理真正管理的不是异常,而是信息出口

看完 Odoo 这一套,很容易得出一个结论:

前端异常处理不是在管理 error 对象,而是在管理错误信息怎样流向用户、开发者和系统自身。

有的错误该进通知; 有的该进业务对话框; 有的该静默吞掉; 有的该在 console 保留完整 traceback。

当系统能把这些出口分清,前端报错才真正从“红字”升级成“运行时治理能力”。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。