前端错误捕获方式
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 上报错误,确保页面卸载时数据不丢失
目录