不用trycatch,如何机智的捕获错误

起源
我们知道,React中有个特性Error Boundary,帮助我们在组件发生错误时显示“错误状态”的UI。

为了实现这个特性,就一定需要捕获到错误。

所以在React源码中,所有用户代码都被包裹在一个方法中执行。

类似如下:

 
 
 
 
  1. function wrapper(func) { 
  2.   try { 
  3.     func(); 
  4.   } catch(e) { 
  5.     // ...处理错误 
  6.   } 

比如触发componentDidMount时:

 
 
 
 
  1. wrapper(componentDidMount); 

本来一切都很完美,但是React作为世界级前端框架,受众广泛,凡事都讲究做到极致。

这不,有人提issue:

 
 
 
 
  1. 你们这样在try catch中执行用户代码会让浏览器调试工具的Pause on exceptions失效。 

Pause on exceptions失效的来龙去脉
Pause on exceptions是什么?

他是浏览器调试工具source面板的一个功能。

开启该功能后,在运行时遇到会抛出错误的代码,代码的执行会自动停在该行,就像在该行打了断点一样。

比如,执行如下代码,并开启该功能:

 
 
 
 
  1. let a = c; 

代码的执行会在该行暂停。

这个功能可以很方便的帮我们发现未捕获的错误发生的位置。

但是,当React将用户代码包裹在try catch后,即使代码抛出错误,也会被catch。

Pause on exceptions无法在抛出错误的用户代码处暂停,因为error已经被React catch了。

除非我们进一步开启Pause on caught exceptions。

开启该功能,使代码在捕获的错误发生的位置暂停。

如何解决
对用户来说,我写在componentDidMount中的代码明明未捕获错误,可是错误发生时Pause on exceptions却失效了,确实有些让人困惑。

所以,在生产环境,React继续使用try catch实现wrapper。

而在开发环境,为了更好的调试体验,需要重新实现一套try catch机制,包含如下功能:

  • 捕获用户代码抛出的错误,使Error Boundary功能正常运行
  • 不捕获用户代码抛出的错误,使Pause on exceptions不失效

这看似矛盾的功能,React如何机智的实现呢?

如何“捕获”错误
让我们先实现第一点:捕获用户代码抛出的错误。

但是不能使用try catch,因为这会让Pause on exceptions失效。

解决办法是:监听window的error事件。

根据GlobalEventHandlers.onerror MDN[1],该事件可以监听到两类错误:

  • js运行时错误(包括语法错误)。window会触发ErrorEvent接口的error事件
  • 资源(如