事情是这个样子的, 我们的项目接入了 sentry, 突然有一天, 飞书上接入的报警机器人开始疯狂报警, 错误瞬间飙升 1w 多, 虽然不影响功能, 但必须要解决啊。
sentry 上报错是这样的
Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.
这很明显嘛! 传入dispatchEvent
的参数不是一个 Event
类型。然后想在本地复现, 因为距离报错版本并没有发新版, 所以想直接在 master 分支上复现。 乌龙问题就这样出现了。
首先本地运行 master 代码, 发现问题并没有复现, 因为认为本地代码和线上代码是一样的, 所以就很纳闷,为什么本地代码和线上打包以后的代码不一样呢? 难道打包平台会做一些其他的操作, 其实想的越多离正确答案就越远 🤣
然后开始本地代码打包, 本地启动一个服务, 发现 http 请求发不出去, 请求的接口路径用的相对路径, 开发的时候使用的代理, 所以这种方法调不通, 放弃
再想, 即然访问线上的代码报错, 那我能不能把从后端请求回来的 bundle.js 替换成本地打包的 bundle.js 呢?这样也方便排错。说干就干, 谷歌搜索大法显神通, 终于发现在 chrome 浏览器上的解决办法了
开发者工具有个 Sources 面板, 这里存放的就是从后端服务器拉取的 bundle.js, 然后找到 Overrides 面板, 这个地方就可以覆盖之前的 bundle.js
- 我们先创建一个空文件夹, 名称随便
- 选择
Select folder for overrides
, 选择刚刚新创建的文件夹, 浏览器顶部会有个弹窗,选择允许 - 允许以后,这个文件夹上边有个
enable local overrides
选项, 默认是选中的, 代表会重写请求, 如果有请求被重写的话, network 面板标题处会有个黄色的三角形代替 - 去 network 面板, 找到 bundle.js 文件, 右键, 选择
Save for overrides
, bundle.js 就会被保存到第一步创建的空文件夹下了 - 把我们本地打包好以后的 bundle.js 替换掉第 4 步下载下来的 bundle.js
- 刷新页面, 就会加载本地的 bundle.js
至此, 之前的想法就实现了, 可是、可是它还是不复现 sentry 上的报错, 问题出在了哪里呢?
到此, 只能认为线上打包代码和本地代码有某些地方不一致。 现在才这么想, 不过也不晚。于是去打包平台日志面板,找到打包时的 git tag, 本地切换代码到相应的 tag, 本地打包, 重复之前的步骤, 报错复现了, 哇, 终于复现了, 老泪纵横。。。
既然问题复现了, 那么问题就好解决了, 最终定位到问题是: 我们的一个小伙伴对事件对象 e 做了一层深度复制, 如下
function test(event) {
const e = cloneDeep(event) // 错误根本
// ...
const eventClone = new e.constructor(e.type, e);
document.dispatchEvent(eventClone);
}
然后把 cloneDeep
去掉, 问题完美解决。
回想整个解错过程, 发现根本不需要如上那么复杂的步骤, 如果业务代码一致, 那么线上打包和本地开发肯定是一致的,如果不一致,那说明本地代码修改过, 这也就是一开始运行 master 代码不报错的原因, 那个 cloneDeep
已经被去掉了。 一开始就陷入了错误的方向,然后越走越远。 最简单的方式应该是切换到相应的 tag, 本地跑程序,错误也会复现, 啊, 那我做了上面那么多是为了什么啊!!!
排错过程也是有收获的, 之前根本不知道 Sources 面板还有如此强大的功能。