我发现了Chrome的一个bug
故事的开始
最近在项目中遇到一个问题,业务逻辑就不在这里介绍了,在排查过程中发现项目里有类似这样一段代码:
fetch(url)
.then((res) => res.text())
.then((text) => {
const data = JSON.parse(text)
})
上面没有对最后一个then
进行catch
操作,我们怀疑这里出了问题,因为text
可能无法解析为合法的JSON
,然而事实上我们的项目里是有全局的错误捕获的:
window.addEventListener('unhandledrejection', (e) => {
sendLog(e)
})
这就奇怪了,华容逢关羽吗?,难道unhandledrejection
事件不仅没有捕获到JSON.parse
的报错还让它走脱了吗?然后我们做了下面这样代码的尝试:
window.addEventListener('unhandledrejection', event => {
console.log('unhandledrejection:', event);
})
Promise.resolve("{")
.then((data) => {
JSON.parse(data)
})
在控制台中出现下下面的报错信息:
Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
...
可以看到这个语法错误是因为JSON.parse
无法成功解析的缘故,确实是没有被unhandledrejection
捕获,难道是这个事件出了问题吗?索性我们直接抛出错误,看这个事件能不能正常工作:
Promise.resolve()
.then(() => {
throw 123
})
控制台出现了下面的信息:
unhandledrejection: 123
说明unhandledrejection
事件是没问题的,那为什么JSON.parse
的错误不能捕获呢?难道是无法捕获语法错误吗?那我们换一种eval
的方式解析JSON
,如果无法捕获语法错误,那么eval
报的语法错误肯定也无法捕获:
Promise.resolve("{")
.then((data) => {
eval(data)
})
我们再看控制台的输出:
unhandledrejection: SyntaxError: Unexpected end of input
蒙圈了吧,这个语法错误居然被捕获到了,我们又想,难道说JSON.parse
的报错很特殊吗?我们又做了下面的尝试:
Promise.resolve("{")
.then((data) => {
try {
JSON.parse(data)
} catch (e) {
throw e
}
})
上面尝试将报错通过try...catch...
捕获到,然后再重新抛出,然后控制台输出:
Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
上面说明错误并没有被捕获到,难道是错误信息有问题?那我们把错误包装一下呢:
Promise.resolve("{")
.then((data) => {
try {
JSON.parse(data)
} catch (e) {
const wraper = new SyntaxError(e.messgae)
wraper.stack = e.stack
throw wraper
}
})
上面创建了一个SyntaxError
的实例wraper
,然后把try...catch...
捕获到的错误信息放到wraper
上,抛出wraper
,控制台输出如下:
unhandledrejection: SyntaxError: Unexpected end of input
看起来错误被捕获到了,我们开始怀疑人生,于是在stackoverflow
提了这个疑问:stackoverflow问题地址[1],内容基本和上文一致,感兴趣的同学可以去看看,另外,对于上面问题在Firefox
和Safari
中并未出现。
有意思的事
在stackoverflow
的评论中,一位叫Kaiido
的开发者说这可能是Chrome
的bug
,建议我去chromium bug报告网址[2]报告一下,没想到我搜索到了类似的报告[3],报告人说他发现window.onerror
和unhandledrejection
这两个事件都无法捕获到JSON.parse
的错误:
window.onerror = async (...args) => {
console.log("onerror")
}
window.addEventListener('unhandledrejection', async function (event) {
console.log("unhandledrejection")
})
var elem = document.getElementById('button');
elem.addEventListener('click', () => { JSON.parse(undefined); })
上面报告人监听的两个事件,说最终都没有被触发,大家也可以试一下,这个例子由于JSON.parse
不在Promise
中,所以window.onerror
是能够捕获的,也就是说报告人的提问不是很恰当,下面维护者的回复就比较搞笑了,这位维护者打开了与报告人相同版本(chrome88)的浏览器和黑暗模式,然后打开chrome
空白页,将上面代码粘贴到控制台执行,然后出现下面报错:
Uncaught TypeError: Cannot read property 'addEventListener' of null
at <anonymous>:11:6
然后维护者让报告人指出问题的所在,大家应该都看得出来,这个报错是因为页面上没有button
元素导致的,与报告人所说的错误没有任何关系,所以我怀疑这个维护者是来搞笑的,时隔几乎一个月,今天上午报告人使用了我在stackoverflow
提出的问题作为例子来向维护者说明问题,目前维护者还未回复,大家可以持续关注下
最后
在上面问题还没解决的情况下,我们最好还是对现有代码做一下审查,对于使用Promise
的地方在then
后面一定要加catch
方法,对于直接使用JSON.parse
的位置,根据对参数的了解情况酌情添加try...catch...
对于前端错误监控平台来说,这也是一个棘手的问题,希望未来能从中看到更好的解决方案。
封面图:愚蠢的美人鱼 by 铁柱呆又呆[4]
参考资料
stackoverflow问题地址: https://stackoverflow.com/questions/68356441/why-unhandledrejection-cant-caught-an-error-from-json-parse
[2]bug报告网址: https://bugs.chromium.org/p/chromium/issues/list
[3]chromium bug报告地址: https://bugs.chromium.org/p/chromium/issues/detail?id=1219363&q=unhandledrejection&can=2
[4]愚蠢的美人鱼 by 铁柱呆又呆: https://www.zcool.com.cn/work/ZNTM0MzQ2NDg=.html