啥?竟然是Chrome的bug,你的前端监控还稳吗?

836 阅读4分钟

故事的开始

最近在项目中遇到一个问题,业务逻辑就不在这里介绍了,在排查过程中发现项目里有类似这样一段代码:

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问题地址,内容基本和上文一致,感兴趣的同学可以去看看,另外,对于上面问题在FirefoxSafari中并未出现。

有意思的事

stackoverflow的评论中,一位叫Kaiido的开发者说这可能是Chromebug,建议我去chromium bug报告网址报告一下,没想到我搜索到了类似的报告,报告人说他发现window.onerrorunhandledrejection这两个事件都无法捕获到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 铁柱呆又呆

关注「码生笔谈」公众号,阅读更多有趣文章