以协同文档为例,我们首先要考虑的是,可能影响用户正常使用的错误。
这种错误使用 window.onerror 捕获同步错误(比如在 ui 渲染前就调用了 ui 的实例)和 window.onunhandledrejection 捕获未被 catch 的异常。
比如在协同文档中,有一个自动保存的功能。用户可能因为网络异常 reject 或者后端返回 500 而 reject。如果没有 catch 并进行 ui 上的提示,那么从用户的角度上来,就会出现明明写了很多东西,但是没有保存上。像这种埋点信息,会在类型里标注是 unhandlerejection,并提高优先级。
除了 “js 异常”外,业务异常更加常见。
比如用户 A、B 在同时编辑标题
用户 A 把节点A的标题从“开始”改成了“启动”。(此时 A 的网络有点卡,还没保存),几乎同一时间,用户 B 把标题改成了 “准备”,B 的网络好,保存成功了,几秒后,A 的保存请求才到服务器。
此时服务器就检测到了冲突,B 的版本号覆盖了 A 的版本号,服务器正常返回 200,但是,业务状态是在 json 中显示为 “merge_confiict”
这个部分对 js 来说,并不是错误。但是对用户 a 来说,它的数据丢失了。所以,我们在统一的 API 封装层里,会检查所有返回的 JSON。一旦发现 code === "MERGE_CONFLICT",我们除了在 UI 上提示用户(比如弹窗让他复制内容)之外,会立刻调用 SDK 的自定义上报接口。
这样的好处就是,我们可以统计到每天发生多少次“保存冲突”。是特定文档冲突多,还是特定用户总是遇到冲突。
这样有助于迭代“冲突合并”的 UI,从“保存失败”,优化为了“检测到了新版本,是否覆盖的友好提示”
框架集成(componentDidCatch):因为我们用的是 React,所以我们用 ErrorBoundary 包裹了主要的画布和工具栏区域。一旦某个“增强组件”崩溃,我们能把它控制在局部,并上报精确的组件堆栈信息,知道是“评论区”崩了还是“属性面板”崩了。
有的时候,用户可能会装各种各样的插件,这些插件会修改 dom 并导致报错,我们会在 sdk 中提供一个 beforeSend 钩子,在错误上报前进行清洗。比如 chrome-extension:// 我们就直接过滤掉不上报。