我發(fā)現(xiàn)了Chrome的一個bug
故事的開始
最近在項目中遇到一個問題,業(yè)務邏輯就不在這里介紹了,在排查過程中發(fā)現(xiàn)項目里有類似這樣一段代碼:
fetch(url)
.then((res) => res.text())
.then((text) => {
const data = JSON.parse(text)
})
上面沒有對最后一個then進行catch操作,我們懷疑這里出了問題,因為text可能無法解析為合法的JSON,然而事實上我們的項目里是有全局的錯誤捕獲的:
window.addEventListener('unhandledrejection', (e) => {
sendLog(e)
})
這就奇怪了,華容逢關(guān)羽嗎?,難道unhandledrejection事件不僅沒有捕獲到JSON.parse的報錯還讓它走脫了嗎?然后我們做了下面這樣代碼的嘗試:
window.addEventListener('unhandledrejection', event => {
console.log('unhandledrejection:', event);
})
Promise.resolve("{")
.then((data) => {
JSON.parse(data)
})
在控制臺中出現(xiàn)下下面的報錯信息:
Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
...
可以看到這個語法錯誤是因為JSON.parse無法成功解析的緣故,確實是沒有被unhandledrejection捕獲,難道是這個事件出了問題嗎?索性我們直接拋出錯誤,看這個事件能不能正常工作:
Promise.resolve()
.then(() => {
throw 123
})
控制臺出現(xiàn)了下面的信息:
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
}
})
上面創(chuàng)建了一個SyntaxError的實例wraper,然后把try...catch...捕獲到的錯誤信息放到wraper上,拋出wraper,控制臺輸出如下:
unhandledrejection: SyntaxError: Unexpected end of input
看起來錯誤被捕獲到了,我們開始懷疑人生,于是在stackoverflow提了這個疑問:stackoverflow問題地址[1],內(nèi)容基本和上文一致,感興趣的同學可以去看看,另外,對于上面問題在Firefox和Safari中并未出現(xiàn)。
有意思的事
在stackoverflow的評論中,一位叫Kaiido的開發(fā)者說這可能是Chrome的bug,建議我去chromium bug報告網(wǎng)址[2]報告一下,沒想到我搜索到了類似的報告[3],報告人說他發(fā)現(xiàn)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); })
上面報告人監(jiān)聽的兩個事件,說最終都沒有被觸發(fā),大家也可以試一下,這個例子由于JSON.parse不在Promise中,所以window.onerror是能夠捕獲的,也就是說報告人的提問不是很恰當,下面維護者的回復就比較搞笑了,這位維護者打開了與報告人相同版本(chrome88)的瀏覽器和黑暗模式,然后打開chrome空白頁,將上面代碼粘貼到控制臺執(zhí)行,然后出現(xiàn)下面報錯:
Uncaught TypeError: Cannot read property 'addEventListener' of null
at <anonymous>:11:6
然后維護者讓報告人指出問題的所在,大家應該都看得出來,這個報錯是因為頁面上沒有button元素導致的,與報告人所說的錯誤沒有任何關(guān)系,所以我懷疑這個維護者是來搞笑的,時隔幾乎一個月,今天上午報告人使用了我在stackoverflow提出的問題作為例子來向維護者說明問題,目前維護者還未回復,大家可以持續(xù)關(guān)注下
最后
在上面問題還沒解決的情況下,我們最好還是對現(xiàn)有代碼做一下審查,對于使用Promise的地方在then后面一定要加catch方法,對于直接使用JSON.parse的位置,根據(jù)對參數(shù)的了解情況酌情添加try...catch...
對于前端錯誤監(jiān)控平臺來說,這也是一個棘手的問題,希望未來能從中看到更好的解決方案。
封面圖:愚蠢的美人魚 by 鐵柱呆又呆[4]
參考資料
stackoverflow問題地址: https://stackoverflow.com/questions/68356441/why-unhandledrejection-cant-caught-an-error-from-json-parse
[2]bug報告網(wǎng)址: 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
