前端异常监控

在前端项目中,由于JavaScript本身是一个弱类型语言,加上浏览器环境的复杂性,网络问题等等,很容易发生错误。做好网页错误监控,不断优化代码,提高代码健壮性是一项很重要的工作。

JS中的Error

通用Error

Error有两个标准属性:

  • Error.prototype.name:错误的名字
  • Error.prototype.message:错误的描述

一个标准方法:

  • Error.prototype.toString:返回表示一个表示错误的字符串

非标准的属性(各个浏览器厂商对于Error都有自己的实现)。比如下面这些属性:

  • Error.prototype.fileName:产生错误的文件名。
  • Error.prototype.lineNumber:产生错误的行号。
  • Error.prototype.columnNumber:产生错误的列号。
  • Error.prototype.stack:堆栈信息。这个比较常用。

Error种类

除了通用的Error构造函数外,JavaScript还有7个其他类型的错误构造函数。

  • InternalError: 创建一个代表Javascript引擎内部错误的异常抛出的实例。 如: “递归太多”。非ECMAScript标准。
  • RangeError: 数值变量或参数超出其有效范围。例子:var a = new Array(-1);
  • EvalError: 与eval()相关的错误。eval()本身没有正确执行。
  • ReferenceError: 引用错误。 例子:console.log(b);
  • SyntaxError: 语法错误。例子:var a = ;
  • TypeError: 变量或参数不属于有效范围。例子:[1,2].split('.')
  • URIError: 给encodeURIdecodeURl()传递的参数无效。例子:decodeURI('%2')

当JavaScript运行过程中出错时,会抛出上8种(上述7种加上通用错误类型)错误中的其中一种错误。错误类型可以通过error.name拿到。

DOMException

除了JavaScript本身运行时会发生的错误。页面中还会有其他的异常,比如错误地操作了DOM。

DOMException有以下三个属性:

  • DOMException.code:错误编号。
  • DOMException.message:错误描述。
  • DOMException.name:错误名称。

Promise产生的异常

在Promise中,如果Promise被reject了,就会抛出异常:PromiseRejectionEvent。下面两种情况都会导致Promise被reject:

  1. 业务代码本身调用了Promise.reject
  2. Promise中的代码出错。

PromiseRejectionEvent的构造函数目前在浏览器中大多都不兼容,这里就不说了。

PromiseRejectionEvent的属性有两个:

  • PromiseRejectionEvent.promise:被reject的Promise。
  • PromiseRejectionEvent.reason:Promise被reject的原因。会传递给reject。Promise的catch中的参数。

捕获错误

window.onerror

当有JS运行时错误触发时,window会触发error事件,并执行window.onerror()

1
window.onerror = function(message, source, lineno, colno, error) { ... }

函数参数:

  • message:错误信息(字符串)。可用于HTML onerror=””处理程序中的event。
  • source:发生错误的脚本URL(字符串)
  • lineno:发生错误的行号(数字)
  • colno:发生错误的列号(数字)
  • error:Error对象

若该函数返回true,则阻止执行默认事件处理函数,如异常信息不会在console中打印。没有返回值或者返回值为false的时候,异常信息会在console中打印。

window.addEventListener('error')

1
window.addEventListener('error', function(event) { ... })

event是ErrorEvent对象的实例。ErrorEvent是事件对象在脚本发生错误时产生,从Event继承而来。由于是事件,自然可以拿到target属性。ErrorEvent还包括了错误发生时的信息:

  • ErrorEvent.prototype.message: 字符串,包含了所发生错误的描述信息。
  • ErrorEvent.prototype.filename: 字符串,包含了发生错误的脚本文件的文件名。
  • ErrorEvent.prototype.lineno: 数字,包含了错误发生时所在的行号。
  • ErrorEvent.prototype.colno: 数字,包含了错误发生时所在的列号。
  • ErrorEvent.prototype.error: 发生错误时所抛出的 Error 对象。

捕获未处理的Promise错误-window.addEventListener('unhandledrejection')

在使用Promise的时候,如果没有声明catch代码块,Promise的异常会被抛出。我们可以通过监听unhandledrejection事件或者window.onunhandledrejection捕获到该异常。

1
window.addEventListener('unhandledrejection', function (event) { ... });

event就是上文提到的PromiseRejectionEvent,我们只需要关注其reason(Promise的reject值)就行

例如:

1
2
3
4
5
6
7
8
9
10
window.addEventListener('unhandledrejection', event => {
// 打印"Hello, Fundebug!"
console.log(event.reason);
});

function foo() {
Promise.reject('Hello, Fundebug!');
}

foo();

例如:

当一个Promise错误最初未被处理,但是稍后又得到了处理,则会触发rejectionhandled事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
window.addEventListener('unhandledrejection', event => {
// 打印"Hello, Fundebug!"
console.log(event.reason);
});

window.addEventListener('rejectionhandled', event => {
// 1秒后打印"rejection handled"
console.log('rejection handled');
});


function foo() {
return Promise.reject('Hello, Fundebug!');
}

var r = foo();

setTimeout(() => {
r.catch(e => {});
}, 1000);

window.onerrorwindow.addEventListener('error')的区别

  1. 事件监听器和事件处理器的区别。监听器只能声明一次,后续的声明会覆盖之前的声明。而事件处理器则可以绑定多个回调函数。

  2. 资源(<img><script>)加载失败时,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。但这些error事件不会向上冒泡到window。不过,这些error事件能被window.addEventListener('error')捕获。也就是说,面对资源加载失败的错误,只能用window.addEventListerner('error')window.onerror无效

实际应用

页面错误监控:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// JS错误处理
window.addEventListener('error', function (event) {
const isResource = event.target && (event.target.src || event.target.href)
if (isResource) {
// 资源错误
// 关键信息上报
} else {
// JS错误
const {message, filename, lineno, colno, error} = event
// 关键信息上报
}
})


// promise错误
window.addEventListener('unhandledrejection', function (event) {
let message = ''
let lineno = ''
let colno = ''
let name = ''
let url = event.target.location.href
let time = getFormatDate()
if (typeof event.reason === 'string') {
message = event.reason
} else if (typeof event.reason === 'object') {
message = event.reason.message
if (event.reason.stack) {
var matchResult = event.reason.stack.match(/at\s+(.+):(\d+):(\d+)/)
if (matchResult) {
name = matchResult[1]
lineno = matchResult[2]
colno = matchResult[3]
}
}
}
// 关键信息上报
})

参考


----------- 本文结束啦感谢您阅读 -----------

赞赏一杯咖啡

欢迎关注我的其它发布渠道