前端错误捕获方式

JavaScript 中不同类型错误的捕获方法

问题

前端开发中会遇到多种类型的错误,如何针对不同错误类型选择合适的捕获方式?

解答

1. try-catch:同步代码错误

// 捕获同步代码中的错误
try {
  const obj = undefined;
  obj.name; // TypeError
} catch (error) {
  console.log('错误类型:', error.name);
  console.log('错误信息:', error.message);
  console.log('错误堆栈:', error.stack);
}

// 注意:try-catch 无法捕获异步错误
try {
  setTimeout(() => {
    throw new Error('异步错误'); // 无法被捕获
  }, 0);
} catch (error) {
  console.log('不会执行');
}

2. window.onerror:全局 JS 运行时错误

// 捕获未被 try-catch 处理的运行时错误
window.onerror = function(message, source, lineno, colno, error) {
  console.log('错误信息:', message);
  console.log('错误文件:', source);
  console.log('行号:', lineno);
  console.log('列号:', colno);
  console.log('错误对象:', error);
  
  // 返回 true 阻止默认错误处理(不在控制台显示错误)
  return true;
};

// 触发错误
undefinedFunction(); // ReferenceError

3. addEventListener(‘error’):资源加载错误

// 捕获资源加载失败(图片、脚本、样式等)
// 必须在捕获阶段处理,因为资源加载错误不会冒泡
window.addEventListener('error', function(event) {
  const target = event.target;
  
  // 判断是否为资源加载错误
  if (target !== window) {
    console.log('资源加载失败:', target.src || target.href);
    console.log('标签名:', target.tagName);
  }
}, true); // 第三个参数必须为 true,使用捕获阶段

// 示例:加载不存在的图片
const img = document.createElement('img');
img.src = 'https://example.com/not-exist.png';
document.body.appendChild(img);

4. unhandledrejection:Promise 错误

// 捕获未处理的 Promise rejection
window.addEventListener('unhandledrejection', function(event) {
  console.log('Promise 错误:', event.reason);
  
  // 阻止默认处理(不在控制台显示错误)
  event.preventDefault();
});

// 触发 Promise 错误
Promise.reject(new Error('Promise 被拒绝'));

// async/await 未捕获的错误也会触发
async function fetchData() {
  throw new Error('async 函数错误');
}
fetchData();

5. React Error Boundary:组件渲染错误

// Error Boundary 组件
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  // 发生错误时更新 state
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  // 记录错误信息
  componentDidCatch(error, errorInfo) {
    console.log('组件错误:', error);
    console.log('组件堆栈:', errorInfo.componentStack);
    // 可以上报到错误监控服务
  }

  render() {
    if (this.state.hasError) {
      return <h1>页面出错了</h1>;
    }
    return this.props.children;
  }
}

// 使用方式
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

6. Vue errorHandler:全局错误处理

// Vue 3
const app = createApp(App);

app.config.errorHandler = (err, instance, info) => {
  console.log('错误:', err);
  console.log('组件实例:', instance);
  console.log('错误来源:', info);
};

// Vue 2
Vue.config.errorHandler = function(err, vm, info) {
  console.log('错误:', err);
  console.log('组件:', vm);
  console.log('信息:', info);
};

7. 统一错误上报封装

// 错误监控初始化
function initErrorMonitor(reportUrl) {
  // JS 运行时错误
  window.onerror = function(msg, url, line, col, error) {
    report({ type: 'js', msg, url, line, col, stack: error?.stack });
    return true;
  };

  // 资源加载错误
  window.addEventListener('error', function(e) {
    if (e.target !== window) {
      report({ 
        type: 'resource', 
        tagName: e.target.tagName,
        src: e.target.src || e.target.href 
      });
    }
  }, true);

  // Promise 错误
  window.addEventListener('unhandledrejection', function(e) {
    report({ type: 'promise', reason: e.reason?.message || e.reason });
    e.preventDefault();
  });

  // 上报函数
  function report(data) {
    const body = JSON.stringify({
      ...data,
      url: location.href,
      userAgent: navigator.userAgent,
      timestamp: Date.now()
    });
    
    // 使用 sendBeacon 保证页面卸载时也能发送
    if (navigator.sendBeacon) {
      navigator.sendBeacon(reportUrl, body);
    } else {
      fetch(reportUrl, { method: 'POST', body, keepalive: true });
    }
  }
}

// 初始化
initErrorMonitor('https://api.example.com/error');

关键点

  • try-catch 只能捕获同步错误,无法捕获异步错误
  • window.onerror 捕获全局 JS 错误,但无法捕获资源加载错误
  • addEventListener(‘error’, fn, true) 必须在捕获阶段才能捕获资源错误
  • unhandledrejection 专门处理未捕获的 Promise rejection
  • Error Boundary 只能捕获子组件渲染、生命周期、构造函数中的错误,无法捕获事件处理和异步代码中的错误
  • 使用 navigator.sendBeacon 上报错误,确保页面卸载时数据不丢失