js
错误SyntaxError
解析时发生语法错误。
window.onerror
捕获不到SyntxError
,一般SyntaxError
在构建阶段,甚至本地开发阶段就会被发现。
比如我们的关键词拼写错误
constt a;
lett b;
varr c;
TypeError
值类型不对,当传入函数的操作数或参数的类型并非操作符或函数所预期的类型时,将抛出一个 TypeError
类型错误。
const nul = null;
console.log(null.value);
ReferenceError
引用未声明的变量。
console.log(undefinedVariable);
RangeError
当一个值不在其所允许的范围或者集合中。
例如不能将数字的有效位数位 1-100,将其设置为 200 就出现RangeError
错误。
const num = 1;
num.toPrecision(200);
以上几个错误(除了SyntaxError
)都可以通过改写window.onerror
方法或添加window.addEventListener('error', function () {})
监听进行捕获。
window.onerror = function (msg, url, lineNo, columnNo, error) {
// 处理错误信息
}
// demo
msg: Uncaught TypeError: Uncaught ReferenceError: a is not defined
error.statck: TypeError: ReferenceError: a is not defined at http://xxxx.js:1:13
window.addEventListener('error', event => (){
// 处理错误信息
}, false);
// true代表在捕获阶段调用,false代表在冒泡阶段捕获。使用true或false都可以,默认为false
ResourceError
资源加载错误
当一项资源(如或
)加载失败,加载资源的元素会触发一个
Event
接口的 error
事件,并执行该元素上的 onerror()
处理函数。
<img id="pic" src="http://picundefined.png" />
<script>
const img = document.getElementById('pic');
img.onerror = function () {
console.log(arguments);
};
script>
这些 error
事件不会向上冒泡到 window
,不过能被 window.addEventListener
在捕获阶段捕获。
但这里需要注意,由于上面提到了 addEventListener
也能够捕获 js
错误,因此需要过滤避免重复上报,判断为资源错误的时候才进行上报。
window.addEventListener('error', event => (){
// 过滤js error
let target = event.target || event.srcElement;
let isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
if (!isElementTarget) return false;
// 上报资源地址
let url = target.src || target.href;
console.log(url);
}, true);
HttpError
Http
请求错误。在浏览器中,我们可以使用xhr
或者fetch
来发送请求。对于一些异常,我们需要通过改写fetch
和xhr
的原生方法,在触发错误时进行自动化的捕获和上报。
改写fetch
方法:
// fetch的处理
function _errorFetchInit() {
if (!window.fetch) return;
let _oldFetch = window.fetch;
window.fetch = function () {
return (
_oldFetch
.apply(this, arguments)
.then((res) => {
if (!res.ok) {
// 当status不为2XX的时候,上报错误
}
return res;
})
// 当fetch方法错误时上报
.catch((error) => {
// error.message,
// error.stack
// 抛出错误并且上报
throw error;
})
);
};
}
对于XMLHttpRequest
的重写:
// xhr的处理
function _errorAjaxInit () {
let protocol = window.location.protocol;
if (protocol === 'file:') return;
// 处理XMLHttpRequest
if (!window.XMLHttpRequest) {
return;
}
let xmlhttp = window.XMLHttpRequest;
// 保存原生send方法
let _oldSend = xmlhttp.prototype.send;
let _handleEvent = function (event) {
try {
if (event && event.currentTarget && event.currentTarget.status !== 200) {
// event.currentTarget 即为构建的xhr实例
// event.currentTarget.response
// event.currentTarget.responseURL || event.currentTarget.ajaxUrl
// event.currentTarget.status
// event.currentTarget.statusText
});
}
} catch (e) {
console.log(`Tool's error: ${e}`);
}
}
xmlhttp.prototype.send = function () {
this.addEventListener('error', _handleEvent); // 失败
this.addEventListener('load', _handleEvent); // 完成
this.addEventListener('abort', _handleEvent); // 取消
return _oldSend.apply(this, arguments);
}
}
关于 responseURL
的说明
需要特别注意的是,当请求完全无法执行的时候,XMLHttpRequest
会收到 status=0
和 statusText=null
的返回,此时 responseURL
也为空string
。
另外在安卓 4.4 及以下版本的 webview
中,xhr
对象也不存在 responseURL
属性。
因此我们需要额外的改写 xhr
的 open
方法,将传入的 url
记录下来,方便上报时带上。
var _oldOpen = xmlhttp.prototype.open;
// 重写open方法,记录请求的url
xmlhttp.prototype.open = function (method, url) {
_oldOpen.apply(this, arguments);
this.ajaxUrl = url;
};
Promise
错误Promise
错误try/catch
不能捕获Promise
中的错误
// try/catch 不能处理 JSON.parse 的错误,因为它在 Promise 中
try {
new Promise((resolve, reject) => {
JSON.parse('');
resolve();
});
} catch (err) {
console.error('in try catch', err);
}
// 需要使用catch方法
new Promise((resolve, reject) => {
JSON.parse('');
resolve();
}).catch((err) => {
console.log('in catch fn', err);
});
async
错误try/catch
不能捕获async
包裹的错误
const getJSON = async () => {
throw new Error('inner error');
};
// 通过try/catch处理
const makeRequest = async () => {
try {
// 捕获不到
JSON.parse(getJSON());
} catch (err) {
console.log('outer', err);
}
};
try {
// try/catch不到
makeRequest();
} catch (err) {
console.error('in try catch', err);
}
try {
// 需要await,才能捕获到
await makeRequest();
} catch (err) {
console.error('in try catch', err);
}
虽然前面两个try/catch
有提示异常,但是提示的都是[object Promist] is not valid JSON
,并不是我们在getJSON
中抛出的异常。对于async
函数,在try/catch
中最好结合await
使用。
import chunk
错误import
其实返回的也是一个promise
,因此使用如下两种方式捕获错误
// Promise catch方法
import(/* webpackChunkName: "incentive" */ './index')
.then((module) => {
module.default();
})
.catch((err) => {
console.error('in catch fn', err);
});
// await 方法,try catch
try {
const module = await import(/* webpackChunkName: "incentive" */ './index');
module.default();
} catch (err) {
console.error('in try catch', err);
}
以上三种其实归结为Promise
类型错误,可以通过unhandledrejection
捕获.
// 全局统一处理Promise
window.addEventListener('unhandledrejection', function (e) {
console.log('捕获到异常:', e);
});
为了防止有漏掉的 Promise
异常,可通过unhandledrejection
用来全局监听Uncaught Promise Error
。
vue
内部发生的错误会被Vue
拦截,因此vue
提供方法给我们处理vue
组件内部发生的错误。
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
};
script error
的解决方式script error
有时也被称为跨域错误。当网站请求并执行一个托管在第三方域名下的脚本时,就可能遇到该错误。最常见的情形是使用 CDN
托管 JS
资源。
其实这并不是一个 JavaScript Bug
。出于安全考虑,浏览器会刻意隐藏其他域的 JS
文件抛出的具体错误信息,这样做可以有效避免敏感信息无意中被不受控制的第三方脚本捕获。
因此,浏览器只允许同域下的脚本捕获具体错误信息,而其他脚本只知道发生了一个错误,但无法获知错误的具体内容。
解决方案 1:(推荐)
添加 crossorigin="anonymous"
属性。
此步骤的作用是告知浏览器以匿名方式获取目标脚本。这意味着请求脚本时不会向服务端发送潜在的用户身份信息(例如 Cookies
、HTTP
证书等)。
添加跨域 HTTP
响应头:
Access-Control-Allow-Origin: *
或者
Access-Control-Allow-Origin: http://test.com
注意: 大部分主流 CDN
默认添加了 Access-Control-Allow-Origin
属性。
完成上述两步之后,即可通过 window.onerror
捕获跨域脚本的报错信息。
解决方案 2
难以在 HTTP
请求响应头中添加跨域属性时,还可以考虑 try/catch
这个备选方案。
在如下示例 HTML
页面中加入 try/catch
:
DOCTYPE html>
<html>
<head>
<title>Test page in http://test.comtitle>
head>
<body>
<script src="http://another-domain.com/app.js">script>
// app.js里面有一个foo方法,调用了不存在的bar方法
<script>
window.onerror = function (message, url, line, column, error) {
console.log(message, url, line, column, error);
};
try {
foo();
} catch (e) {
console.log(e);
throw e;
}
script>
body>
html>
// 运行输出结果如下:
=> ReferenceError: bar is not defined
at foo (http://another-domain.com/app.js:2:3)
at http://test.com/:15:3
=> “Script error.”, “”, 0, 0, undefined
可见 try catch 中的 Console 语句输出了完整的信息,但 window.onerror 中只能捕获“Script error”。根据这个特点,可以在 catch 语句中手动上报捕获的异常。