Javascript / React window.onerror被触发两次

时间:2018-05-06 15:20:10

标签: javascript html reactjs

我希望全局捕获React应用程序中的错误。

但每次错误被捕获/转发两次到我的注册功能。

示例代码:

window.onerror = (msg, url, lineNo, columnNo, error) => {
console.log(msg)
  alert(msg)
}

class TodoApp extends React.Component {
  constructor(props) {
    super(props)


  }

  render() {

    return (
      <button onClick={(e)=>{
        console.log("clicked")
        null.bla
      }}>
        Create an error

      </button>
    )
  }
}

ReactDOM.render(<TodoApp />, document.querySelector("#app"))

这是一个JS小提琴: https://jsfiddle.net/dmxur0rc/4/

控制台只显示一个“点击”日志,因此它不是触发两次的按钮,而是错误事件。

7 个答案:

答案 0 :(得分:3)

您需要从错误处理程序返回true,否则将触发默认错误处理程序:

https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror

  

当函数返回true时,这会阻止触发默认事件处理程序。

另请注意,其他错误处理程序可能会通过addEventHandler生效。

答案 1 :(得分:0)

由于在JSFiddle上运行它的性质,它看起来可能会发射两次。在正常的构建过程中(使用webpackbabel)代码与此类脚本错误应该无法转换。

答案 2 :(得分:0)

它是known react error,与错误边界的实现有关。

答案 3 :(得分:0)

我找到了适用于所有情况的基本解决方案。

事实证明,该对象在所有调用中都是相同的,您可以设置一些东西使其完全匹配,或者可以将自定义属性附加到错误对象上...

诚然,这只能与window.addEventListener('error',function ...)一起使用,因为您将获得真正的错误对象作为参数,而不是window.onerror = function ...会获取数据部分,例如message和lineNumber而不是实际错误。

这基本上就是我的使用方式:

window.addEventListener('error', function (event) {
  if (event.error.hasBeenCaught !== undefined){
    return false
  }
  event.error.hasBeenCaught = true
  // ... your useful code here
})

如果两次被相同的错误调用,它将在进入有用代码之前退出,每个错误仅执行一次有用代码。

答案 4 :(得分:0)

另一种方法是将上一个错误的消息存储在状态中,并检查第二次发生的时间。

export default MyComponent extends React.Component{
  constructor(props){
    super(props);

    this.state = {message: null};
  }

  componentDidMount(){
     const root = this;
     window.onerror = function(msg, url, line, column, error){
        if(root.state.message !== msg){
           root.setState({message: msg});

           // do rest of the logic
        }
     }
  }
}
  

但是无论如何,最好使用React Error Boundaries。而且你可以   在错误内部实施此全局javascript错误处理   边界分量。哪里都可以捕获js错误(通过   window.onerror)和React错误(使用componendDidCatch)。

答案 5 :(得分:0)

我使用此错误边界来处理React和全局错误。这是来自React documentation的一些建议:

  • 错误边界是React组件,可在其子组件树的任何位置捕获JavaScript错误,记录这些错误,并显示后备UI,而不是崩溃的组件树。
  • 如果类组件定义了(或 两者)的生命周期方法静态getDerivedStateFromError()或 componentDidCatch()。
  • 仅使用错误边界从意外异常中恢复; 不要尝试将它们用于控制流。
  • 请注意,错误边界仅捕获树中位于其下方的组件中的错误;错误边界本身无法捕获错误。


class ErrorBoundary extends React.Component {

      state = {
        error: null,
      };
      lastError = null;

      // This lifecycle is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
      static getDerivedStateFromError(error) {
        // Update state so the next render will show the fallback UI.
        return {
          error,
        };
      }

      componentDidMount() {
        window.onerror = (msg, url, line, column, error) => {
          this.logError({
            error,
          });
        };
      }

      // getDerivedStateFromError() is called during the “render” phase, so side-effects are not permitted. For those use cases, use componentDidCatch() instead.

      // This lifecycle is invoked after an error has been thrown by a descendant component. It receives two parameters:

      // error - The error that was thrown.
      // info - An object with a componentStack key containing
      componentDidCatch(error, info) {
        // avoid calling log error twice
        if (this.lastError && this.lastError.message === this.state.error.message) {
          return true;
        }
        // Example "componentStack":
        //   in ComponentThatThrows (created by App)
        //   in ErrorBoundary (created by App)
        //   in div (created by App)
        //   in App
        // logComponentStackToMyService(info.componentStack);
        this.logError({
          error,
          info,
        });
      }

      async logError({
        error,
        info
      }) {
        this.lastError = error;

        try {
          await fetch('/error', {
            method: 'post',
            body: JSON.stringify(error),
          });
        } catch (e) {}
      }

      render() {
        if (this.state.error) {
          return  display error ;
        }

        return this.props.children;
      }

}

答案 6 :(得分:0)

正如其他答案中提到的,问题出在 DEV 模式下的 React 中。在这种模式下re-throws all exceptions可以“改善调试体验”。

我看到 4 种不同的错误情况

  1. 正常的 JS 错误(例如,来自事件处理程序,如问题中所述)。

    这些由 React 的 window.onerror 发送给 invokeGuardedCallbackDev 两次

  2. render 期间发生的 JS 错误,并且组件树中没有 React 的 error boundary

    与场景 1 相同。

  3. render 期间发生的 JS 错误,并且在组件树的某处有一个错误边界

    这些被window.onerror发送给invokeGuardedCallbackDev一次,但也会被错误边界捕获{ {1}}。

  4. Promise 中的 JS 错误,未处理。

    这些不会发送到 componentDidCatch,而是发送到 window.onerror。而且这种情况只会发生一次,所以没问题。

我的解决方法

window.onunhandledrejection