前端错误监控从0到1:3个方案让Bug无处遁形

先看最基础的监控方案:捕获全局运行时错误。

方案一:window.onerror + try-catch 兜底

为什么这么写?因为window.onerror只能捕获同步错误和部分异步错误,但网络请求错误、Promise内部的reject它管不了。我们需要两层防御。

“`javascript
// 全局错误监听
window.onerror = function(message, source, lineno, colno, error) {
// 过滤掉第三方脚本错误(比如广告SDK)
if (source && source.includes(‘cdn.example.com’)) {
return; // 这些错误我们管不了,也修不了
}

const errorInfo = {
type: ‘runtime’,
message: message,
source: source,
lineno: lineno,
colno: colno,
stack: error?.stack || ‘no stack’,
// 关键是带上用户环境
ua: navigator.userAgent,
url: window.location.href,
timestamp: Date.now()
};

// 异步上报,避免影响主流程
reportError(errorInfo);
return true; // 阻止默认处理
};

// 先定义reportError函数,实际项目中可替换为你的上报逻辑
function reportError(info) {
// 比如用sendBeacon异步上报,不影响主流程
navigator.sendBeacon(‘/log’, JSON.stringify(info));
}

// 再补一层try-catch包裹关键业务,注意异步函数要用async/await
async function safeExecute(fn, context = ‘unknown’) {
try {
return await fn();
} catch (error) {
reportError({
type: ‘business’,
context: context,
message: error.message,
stack: error.stack,
errorType: ‘network’
});
// 降级处理:比如显示友好的错误提示
showErrorToast(&#82

16;页面出了点小问题,请刷新试试’);
}
}
“`

这个设计真的反人类:window.onerror的跨域脚本错误只能拿到”Script error.”,啥信息都没有。解决方案是给script标签加crossorigin="anonymous"属性,但前提是服务端必须返回Access-Control-Allow-Origin头。要是CDN不支持,那就只能白屏伺候了。后来我们换了个策略:用addEventListener监听error事件。

(开篇先放一张前端错误分类脑图,包含运行时错误、Promise异常、资源加载失败、接口错误四大类,用不同颜色标注各自的捕获方式)

方案二:Promise异常捕获——这个坑最大

刚开始我也以为,有了window.onerror,Promise的reject应该自动能捕获。结果发现,Promise内部的异常只有显式调用reject或者throw new Error时才会触发onerror。更骚的是,如果Promise没有挂.catch,浏览器控制台会报UnhandledPromiseRejection,但onerror不一定能捕获到。

另一个坑:async/await函数的错误,如果不用try-catch包裹,会变成Promise.reject,然后就人间蒸发了。

“`javascript
// 全局捕获未处理的Promise拒绝
window.addEventListener(‘unhandledrejection’, function(event) {
const reason = event.reason;
// 阻止默认的打印到控制台
event.preventDefault();

reportError({
type: ‘promise’,
message: reason?.message || String(reason),
stack: reason?.stack || ‘no stack’,
isPromise: true
});
});

// 封装一个安全的async函数
function createSafeAsync(asyncFn) {
return function(…args) {
return asyncFn(…args).catch(error => {
reportError({
type: ‘async’,r /> context: asyncFn.name || ‘anonymous’,
message: error.message,
stack: error.stack
});
// 返回null时,调用方必须检查返回值,否则可能引发后续错误
// 如果业务需要,可以返回一个默认值对象,比如 { data: null, error: true }
return null; // 返回默认值,调用方记得判空
});
};
}

// 使用示例
const safeFetchUser = createSafeAsync(async (userId) => {
const response = await fetch(`/api/user/${userId}`);
if (!response.ok) throw new Error(‘用户数据获取失败’);
return response.json();
});
“`

有个技巧:生产环境建议开启unhandledrejection的捕获,但开发阶段最好别屏蔽,否则你永远不知道代码里埋了多少未处理的Promise。我们团队的做法是:开发环境让浏览器打印,线上统一上报。

(中间放一张错误上报流程时序图,展示从错误发生、捕获、格式化、上报到后端存储、告警触发的完整链路,标注每个环节耗时)

方案三:资源加载失败——被忽略的隐形杀手

另一个容易被忽略的是图片、CSS、JS文件加载失败。这些错误不会被window.onerror捕获,但会导致页面功能异常。比如某个图标库CDN挂了,按钮全变方块。官方文档这段文档不够清晰,其实就一句话:监听error事件并过滤。

“`javascript
// 捕获资源加载错误(图片、字体、脚本等)
document.addEventListener(‘error’, function(event) {
const target = event.target;
// 排除window.onerror的重复捕获
if (target === window) return;

const errorInfo = {
type: ‘resource’,
resourceType: target.tagName.toLowerCase(),
src: target.src || target.href || ‘unknown’,
// 判断资源URL,兼容Request对象
url: target.src || target.href || (target.url || ‘unknown’),
errorType: ‘resource’,
timestamp: Date.now()
};

reportError(errorInfo);
}, true); // 捕获阶段监听,确保能抓到资源错误

// 重写fetch,捕获网络请求错误
const originalFetch = window.fetch;
window.fetch = function(…args) {
return originalFetch.apply(this, args).catch(error => {
reportError({
type: ‘network’,
url: args[0].url || args[0], // 兼容Request对象和字符串
method: args[1]?.method || ‘GET’,
message: error.message,
errorType: ‘network’
});
throw error; // 重新抛出,让业务代码自己处理
});
};
“`

有个小坑:监听error事件时,一定要在捕获阶段(第三个参数传true),否则资源加载错误根本不会冒泡到document上。另外,重写fetch时别忘了把原来的方法存起来,不然递归调用就炸了。

(最后放一张错误分类汇总表,列出每种错误类型的捕获方式、上报时机、注意事项,方便读者对照参考)

总结一下:前端错误监控不是加一行代码就能搞定的事。你得考虑同步、异步、Promise、资源加载、跨域等各种场景,还得处理上报时机、错误去重、用户环境信息。但别怕,按上面三个方案一步步来,至少能覆盖90%的线上问题。剩下的10%,就靠你写代码时多留个心眼了。

滚动至顶部