在上一篇文章“如何及时发现网页的隐形错误”中我们讲了,前端有哪些常见的异常,以及如今监控获取这些异常的方法,今天我们就来讲讲我是如何来监控我的JavaScript异常的。
浏览器侧的异常分为两种类型
XMLHttpRequest、Fetch()
的方式来请求的 http
资源。<img> 、<script>、<video>、<audio>、<iframe>
等标签加载的资源。既然如此,那就先从JavaScript异常下手
我们都知道获取异常信息的常见几种方式是
我在这里选择选择的是使用JavaScript的window.addEventListener()
监听error
、unhandledrejection
。
window.addEventListener(error和unhandledrejection)
可以捕获全局范围内发生的未处理异常,无论是同步还是异步代码而且错误信息足够详细并且处理起来方便。具体代码:
// 导出一个函数,用于创建 JS 错误监视器
export function createJsErrorMonitor(options: JsErrorMonitorOptions) {
function getBrowserWindow() {
return window;
}
// 获取浏览器窗口
const window = getBrowserWindow();
// 如果没有windown,则返回
if (!window) {
return;
}
// 定义处理错误和拒绝的函数
const handleError = (e: ErrorEvent) => {
// 调用 onReport 函数,报告 JS 错误
options.onReport({
eventType: EventType.JS_ERROR,
data: formatError(e),
});
};
const handleRejection = (e: PromiseRejectionEvent) => {
// 调用 onReport 函数,报告 JS 错误
options.onReport({
eventType: EventType.JS_ERROR,
data: formatError(e),
});
};
// 定义销毁监听器的函数
const destroyListeners = () => {
// 移除 error 事件监听器
window.removeEventListener('error', handleError);
// 移除 unhandledrejection 事件监听器
window.removeEventListener('unhandledrejection', handleRejection);
};
// 捕获异步 error
// 添加 error 事件监听器
window.addEventListener('error', handleError);
// 添加 unhandledrejection 事件监听器
window.addEventListener('unhandledrejection', handleRejection);
// 返回销毁监听器的函数
return {
destroy: destroyListeners,
};
}
但是我们需要注意的是,我们的代码在处理跨域脚本时,还存在一些问题
假设我们要对一段浏览器跨域请求的代码进行监控效果会是怎么样呢?
示例:
<!-- 监控脚本 -->
<script src="监控代码"></script>
<script>
// 创建 JavaScript 错误监控
Monitor.createJsErrorMonitor({
onReport: (e) => {
console.log(e);
}
});
</script>
<!-- 示例脚本,模拟跨域错误 -->
<script src="https://example.com/another-nonexistent.js"></script>
<!-- 带 crossorigin 属性的示例脚本 -->
<script src="https://example.com/another-nonexistent.j" crossorigin="anonymous"></script>
结果是代码会出现异常无法捕捉的情况
我们的第一个 script 的异常没有被监控程序捕获,但是第二个却可以。你可能会问这是为什么呢?
这是因为浏览器跨域规则的限制,在这种情况下捕获到的 ErrorEvent 没有任何有价值的信息。(只能拿到一个模糊的 Script Error 0)。
但是解决方案很简单,我们只需要将相应的 script 标签增加一条 crossorigin="anonymous" 属性即可
<script src="xxxxx.js" crossorigin="anonymous"></script>
而在真实的 webpack 工程化环境中,我们不应该也不可能去一一的手动修改它们,而是会通过编写一个 webpack 插件,hook 到 html-webpack-plugin
的 alterAssetTagGroups
生命周期钩子上为标签增加属性,
代码如下:
import webpack from 'webpack';
import { Hooks } from 'html-webpack-plugin';
export class AddAnonymousWebpackPlugin {
apply(compiler: webpack.Compiler) {
compiler.hooks.compilation.tap('AddAnonymousWebpackPlugin', (compilation) => {
// 通过最终的 webpack 配置的 plugins 属性,根据插件的 constructor.name 拿到 html-webpack-plugin 实例
const HtmlWebpackPluginInstance: any = compiler.options.plugins
// 获取插件构造函数
.map(({ constructor }) => constructor)
// 查找HtmlWebpackPlugin构造函数
.find(constructor => constructor && constructor.name === 'HtmlWebpackPlugin');
if (HtmlWebpackPluginInstance) {
// 获取 html-webpack-plugin 所有的 hooks
const hooks = HtmlWebpackPluginInstance.getHooks(compilation) as Hooks;
// 在插入标签之前做些什么
hooks.alterAssetTagGroups.tap(
'AddAnonymousWebpackPlugin', (data) => {
// 拿到所有的标签,如果是 script 标签,并且满足我们的匹配函数,则将其 attributes.crossorigin = "anonymous"
data.headTags.forEach(tag => {
if (tag.tagName === 'script') {
// 为script标签添加crossorigin属性
tag.attributes.crossorigin = "anonymous";
}
});
return data;
},
);
}
});
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。