addEventListener 回调中的错误处理

Error handling inside addEventListener callback

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

我的第一反应是将 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中你可以覆盖全局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

围绕现有 functions/methods 的 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>

其中一个原因可以为 like afterThrowing or afterFinally 实现基于原型的抽象。然后上面的 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>