【问题标题】:Error handling inside addEventListener callbackaddEventListener 回调中的错误处理
【发布时间】:2021-11-05 15:03:56
【问题描述】:

如果开发人员想要拥有顶级错误处理功能,他们应该如何构建他们的程序?

我的第一反应是在 main 函数中封装一个 try..catch,但是,这不会触发回调错误吗?

try {
  main();
} catch(error) {
  alert(error)
}

function main() {
  
  // This works
  throw new Error('Error from main()');
  
  document.querySelector('button').addEventListener('click', function() {
   // This doesn throw
   throw new Error ('Error from click callback');
  })
  
}
<button>
  Click me to see my callback error
</button>

【问题讨论】:

    标签: javascript error-handling


    【解决方案1】:

    围绕已经存在的函数/方法的 Try-catch 功能可以通过包装器方法得到最好的实现。

    对于 OP 的用例,需要一个修改包装函数,该函数明确针对“投掷后”的处理...

    // - try-catch wrapper which specifically
    //   targets the handling of "after throwing".
    function afterThrowingModifier(proceed, handler, target) {
      return function (...argsArray) {
        let result;
        try {
          result = proceed.apply(target, argsArray);
        } catch (exception) {
          result = handler.call(target, exception, argsArray);
        }
        return result;
      }
    }
    
    function failingClickHandler(/* event */) {
      throw new Error('Error from click callback');
    }
    function afterTrowingHandler(error, [ event ]) {
      const { message, stack } = error
      const { type, currentTarget } = event;
      console.log({
        error: { message, stack },
        event: { type, currentTarget },
      });
    }
    
    function main() {
      document
        .querySelector('button')
        .addEventListener('click', afterThrowingModifier(
          failingClickHandler, afterTrowingHandler
        ));
    }
    main();
    body { margin: 0; }
    .as-console-wrapper { min-height: 85%!important; }
    <button>
      Click me to see my callback error
    </button>

    其中一个原因可以为function modifying failure handling 实现基于原型的抽象,例如afterThrowingafterFinally。然后上面的main 示例代码更改为更具表现力的内容,例如...

    function afterTrowingHandler(error, [ event ]) {
      const { message, stack } = error
      const { type, currentTarget } = event;
      console.log({
        error: { message, stack },
        event: { type, currentTarget },
      });
    }
    
    function main() {
      document
        .querySelector('button')
        .addEventListener('click', (function (/* event */) {
    
          throw new Error('Error from click callback');
    
        }).afterThrowing(afterTrowingHandler));
    }
    main();
    body { margin: 0; }
    .as-console-wrapper { min-height: 85%!important; }
    <button>
      Click me to see my callback error
    </button>
    
    <script>
      (function (Function) {
    
        function isFunction(value) {
          return (
            typeof value === 'function' &&
            typeof value.call === 'function' &&
            typeof value.apply === 'function'
          );
        }
        function getSanitizedTarget(value) {
          return value ?? null;
        }
    
        function afterThrowing/*Modifier*/(handler, target) {
          target = getSanitizedTarget(target);
    
          const proceed = this;
          return (
    
            isFunction(handler) &&
            isFunction(proceed) &&
    
            function afterThrowingType(...argumentArray) {
              const context = getSanitizedTarget(this) ?? target;
    
              let result;
              try {
                // try the invocation of the original function.
    
                result = proceed.apply(context, argumentArray);
    
              } catch (exception) {
    
                result = handler.call(context, exception, argumentArray);
              }
              return result;
            }
    
          ) || proceed;
        }
        // afterThrowing.toString = () => 'afterThrowing() { [native code] }';
    
        Object.defineProperty(Function.prototype, 'afterThrowing', {
          configurable: true,
          writable: true,
          value: afterThrowing/*Modifier*/
        });
    
      }(Function));
    </script>

    【讨论】:

    • 这种不是没必要吗?
    • 这简直太棒了!从来没有想过使用这样的回调。
    【解决方案2】:

    在 javascript 中,您可以覆盖全局 onerror,捕获大部分错误:

    window.onerror = function(message, source, lineno, colno, error) { ... };
    

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

    在你的情况下:

        window.onerror = function(message, source, lineno, colno, error) { 
            console.error(message);
            alert(message);
            return false
        };
        
        function main() {
          
          // This works
          throw new Error('Error from main()');
          
          document.querySelector('button').addEventListener('click', function() {
           // This doesn throw
           throw new Error ('Error from click callback');
          })
        }
        
        main();
    

    一些额外的信息: https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror

    如果 Promise 会引发错误,则添加在问题之后,让我们测试一下:

    window.onerror = (message, source, lineno,colno,error)=>{
        console.error(`It does!, ${message}`);
    };
    const aFn = ()=>{
        return new Promise((resolve)=>{
            setTimeout(()=>{
                throw new Error("whoops")
            }, 3000);
        });
    }
    aFn();
    

    结果:

    VM1163:2 It does!, Script error.
    window.onerror @ VM1163:2
    error (asynchroon)
    (anoniem) @ VM1163:1
    VM1163:7 Uncaught Error: whoops
        at <anonymous>:7:19
    

    【讨论】:

    • 这很有趣!这会捕获基于承诺的错误吗?例如,如果 main() 是一个异步函数。
    • @Toxnyc ... 很可能不是,因为承诺的一个基本特征是提前退出承诺(链)和处理(因此在捕获之前)异常/错误/原因。当然,最顶层的异常处理程序可以实现最终再次抛出。但它应该有什么好处呢?
    • @PeterSeliger 这很容易测试,所以我用测试更新了问题。但简短的回答,确实如此。原因是 Javascript 在每个错误上都会调用 window.onerror。
    • @PeterSeliger 要回答捕获顶级有什么好处,这可能是日志记录。我们一直将它用于记录目的。为了正确处理错误,您可以在 promise 中使用 try...catch 并返回拒绝。但据我了解,这不是问题。
    • 感谢大家参与讨论,他们真的很有帮助!我意识到我的帖子的标题有点误导。我一直在寻找一种方法来捕获所有错误(只是将它们显示给用户),addEventListener 和基于 promise 的 func 只是示例。 FWIW,window.onerror 最接近我的需要,所以我会接受这个作为答案。
    猜你喜欢
    • 2016-12-21
    • 1970-01-01
    • 2017-07-14
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    • 1970-01-01
    • 1970-01-01
    • 2022-12-03
    相关资源
    最近更新 更多