1:前言
继上一篇 异常类型 的介绍与简单处理方案,这一篇将继续深挖错误是如何被捕获的。
通过阅读这篇文章,你将了解到:
- 不同类型的错误:包括同步错误、异步错误、引用资源错误、资源不存在错误、DOM 相关错误和
Promise
错误。 - 各类错误捕获方法的适用场景:了解在不同场景下使用哪种捕获方法最为合适。
- 具体示例与实践:通过具体的代码示例,直观地理解各类错误捕获方法的使用方法和效果。
无论你是初学者还是有经验的开发者,这篇文章都将为你提供系统化的错误捕获知识,帮助你理解市面上众多的监控软件的运行逻辑。
文章中每个结论都有可验证实例绝非泛泛而谈,废话不说我将逐一介绍,请随我一起看下去!
2:try catch
2.1:同步错误
// 同步错误
try {
var arr = []
arr.length = -1
} catch (err) {
console.log('err', err) // err RangeError: Invalid array length
}
try {
b.length
} catch (err) {
console.log('err', err) // err ReferenceError: b is not defined
}
try {
var foo = undefined
foo.substring(1)
} catch (err) {
console.log('err', err) // err TypeError: Cannot read properties of undefined (reading 'substring')
}
console.log('start')
try {
console.log('run')
b.length
} catch (err) {
console.log('err', err)
}
console.log('end')
start
run
err ReferenceError: b is not defined
end
通过上面的实例我们可以知道:try
块里的同步错误都被正确捕获并且不影响代码正常执行
2.2:异步错误
// 异步错误
var a = null
console.log('start')
try {
setTimeout(() => {
console.log('我是一个异步错误:', a.splice(','))
})
} catch (err) {
console.log('err', err)
}
console.log('end')
对异步无能为力
2.3:promise错误
try {
Promise.reject('promise error1')
new Promise((resolve, reject) => {
reject('promise error2')
})
new Promise((resolve) => {
resolve()
}).then(() => {
throw 'promise error3'
})
} catch (err) {
console.log('err', err) // err RangeError: Invalid array length
}
对于promise也是无能为力
2.4:结论
try
块可以捕获同步错误且不影响代码正常执行流,但异步错误无能为力,同样的 promise
也是无能为力
3:window.onerror
3.1:同步错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Window.onerror Example</title>
</head>
<body>
<script>
// 定义全局错误处理函数
/**
* @param {String} msg 错误信息
* @param {String} url 出错文件
* @param {Number} row 行号
* @param {Number} col 列号
* @param {Object} error 错误详细信息
*/
window.onerror = function (msg, url, row, col, error) {
console.log('我捕获了一个同步步错误', {
msg,
url,
row,
col,
error
})
// 返回 true 表示错误已经被处理,不再向控制台输出
return true
}
console.log('start')
// 触发一个同步错误
function aaa() {
var arr = []
arr.length = -1
}
aaa()
console.log('end')
</script>
</body>
</html>
可以捕获同步错误,但很可惜的是js
执行被无条件阻断,这一点不及 try catch
3.2:异步错误
// 异步错误
/**
* @param {String} msg 错误信息
* @param {String} url 出错文件
* @param {Number} row 行号
* @param {Number} col 列号
* @param {Object} error 错误详细信息
*/
console.log('start')
setTimeout(() => {
let a = null
console.log('我是一个异步错误:', a.splice(','))
})
console.log('end')
window.onerror = function (msg, url, row, col, error) {
console.log('我捕获了一个异步错误', {
msg,
url,
row,
col,
error
})
return true
}
运行上面的代码我们可以看出
window.onerror
貌似可以捕捉到异步错误,而且报错信息很全
3.3:引用资源的错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body></body>
<script type="text/javascript">
window.onerror = function (msg, url, row, col, error) {
console.log('我捕获一个引用资源错误:', {
msg,
url,
row,
col,
error
})
return true
}
</script>
<script src="./outSideSource.js"></script>
</html>
// outSideSource.js
var arr = []
arr.length = -1
从上方可以看出window.onerror
可以捕获一个引用资源的报错,但如果是一个异步的资源可以吗?
// outSideSource.js
setTimeout(() => {
var arr = []
arr.length = -1
}, 5000)
从上方可以看出window.onerror
可以捕获一个异步的资源的报错,假设我们引用的资源不存在是否会被捕获?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body></body>
<script type="text/javascript">
window.onerror = function (msg, url, row, col, error) {
console.log('我捕获一个引用资源错误:', {
msg,
url,
row,
col,
error
})
return true
}
</script>
<script src="./abcd.js"></script>
</html>
可以看到对于不存在的资源确实无能为力
3.4:dom资源错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<script type="text/javascript">
window.onerror = function (msg, url, row, col, error) {
console.log('我捕获一个引用资源错误:', {
msg,
url,
row,
col,
error
})
return true
}
</script>
<body>
<img src="./abcd.js" />
</body>
</html>
可以看到对于dom上的错误也是无能为力
3.5:promise错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body></body>
<script type="text/javascript">
window.onerror = function (msg, url, row, col, error) {
console.log('我捕获一个引用资源错误:', {
msg,
url,
row,
col,
error
})
return true
}
Promise.reject('promise error1')
new Promise((resolve, reject) => {
reject('promise error2')
})
new Promise((resolve) => {
resolve()
}).then(() => {
throw 'promise error3'
})
</script>
</html>
可以看到对于promise错误也是无能为力
3.6:结论
window.onerror
可以捕获同步、异步、引用资源的内部错误。但是如果引用资源不存、dom
上的错误、promise
错误的捕获是无能为力的
4:window.addEventListener
4.1:同步错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Window.onerror Example</title>
</head>
<body>
<script>
window.addEventListener(
'error',
(error) => {
console.log('我捕获一个同步错误:', error)
},
true
)
console.log('start')
// 触发一个同步错误
function aaa() {
var arr = []
arr.length = -1
}
aaa()
console.log('end')
</script>
</body>
</html>
可以看出是可以捕获到同步错误
4.2:异步错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Window.onerror Example</title>
</head>
<body>
<script>
window.addEventListener(
'error',
(error) => {
console.log('我捕获一个异步错误:', error)
},
true
)
console.log('start')
// 触发一个同步错误
setTimeout(() => {
let a = null
a.splice(',')
})
console.log('end')
</script>
</body>
</html>
可以看出是可以捕获到异步错误
4.3:引用资源的错误
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body></body>
<script type="text/javascript">
window.addEventListener(
'error',
(error) => {
console.log('我捕获引用资源同步错误:', error)
},
true
)
</script>
<script src="./outSideSource.js"></script>
</html>
// outSideSource.js
var arr = []
arr.length = -1
同步资源可以,我们试试异步资源报错
// outSideSource.js
setTimeout(() => {
var arr = []
arr.length = -1
}, 5000)
4.4:不存在资源的错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body></body>
<script type="text/javascript">
window.addEventListener(
'error',
(error) => {
console.log('我捕获一个引用资源错误:', error)
},
true
)
</script>
<script src="./abcd.js"></script>
</html>
可以看出对于资源不存在的情况也可以捕获
4.5:dom资源错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<img src="./abcd.js" />
</body>
<script type="text/javascript">
window.addEventListener(
'error',
(error) => {
console.log('我捕获一个引用资源错误:', error)
},
true
)
</script>
</html>
以上可以看出Dom
图片资源错误也可以捕捉,非常棒!
4.6:unhandledrejection的错误
window.addEventListener('unhandledrejection', function (e) {
e.preventDefault()
console.log('我知道 promise 的错误了:', e.reason)
return true
})
Promise.reject('promise error1')
new Promise((resolve, reject) => {
reject('promise error2')
})
new Promise((resolve) => {
resolve()
}).then(() => {
throw 'promise error3'
})
Promise.reject('promise error4').catch((e) => console.log(e))
new Promise((resolve, reject) => {
reject('promise error5')
}).catch((e) => console.log(e))
以上可以看出unhandledrejection
不能完全捕获reject
4.7:结论
window.addEventListener
这个api
很是强大,只有promise
的部分reject
不能正确捕获,其余的都可以正常捕获。
也可以告诉我们如果在监控系统中,最好不要去做catch
动作,而是让这个问题无阻碍的暴露给捕获器。否则捕获器可能捕获不到已经出现的错误,导致关键信息丢失。
具体参见MDN
5:总结
5.1:先看一个图表
捕获方法 | 同步错误 | 异步错误 | 引用资源错误 | 资源不存在错误 | Dom相关错误 | promise错误 |
---|---|---|---|---|---|---|
try_catch | √ | x | x | x | x | x |
window.onerror | √ | √ | √ | x | x | x |
window.addEventListener | √ | √ | √ | √ | √ | √(部分) |
5.2:结论
-
try...catch
- 同步错误:可以捕获同步代码中的错误。
- 异步错误:无法捕获异步代码中的错误(如
setTimeout
、setInterval
中的错误)。 - 引用资源错误:无法捕获引用资源(如脚本、样式表)加载错误。
- 资源不存在错误:无法捕获资源加载失败(如
img
标签的地址不对)错误。 - DOM相关错误:无法捕获 DOM 操作中的错误。
- Promise错误:无法捕获未处理的
Promise
错误。
-
window.onerror
- 同步错误:可以捕获同步代码中的错误。
- 异步错误:可以捕获异步代码中的错误(如
setTimeout
、setInterval
中的错误)。 - 引用资源错误:可以捕获引用资源(如脚本、样式表)加载错误。
- 资源不存在错误:无法捕获资源加载失败(如
img
标签的地址不对)错误。 - DOM相关错误:无法捕获 DOM 操作中的错误。
- Promise错误:无法捕获未处理的
Promise
错误。
-
window.addEventListener ('error', ...) / ('unhandledrejection', ...)
- 同步错误:可以通过
window.addEventListener('error', ...)
捕获同步代码中的错误。 - 异步错误:可以通过
window.addEventListener('error', ...)
捕获异步代码中的错误。 - 引用资源错误:可以通过
window.addEventListener('error', ...)
捕获引用资源(如脚本、样式表)加载错误。 - 资源不存在错误:可以通过
window.addEventListener('error', ...)
捕获资源加载失败(如img
标签的地址不对)错误。 - DOM相关错误:可以通过
window.addEventListener('error', ...)
捕获 DOM 操作中的错误。 - Promise错误:可以通过
window.addEventListener('unhandledrejection', ...)
捕获未处理的Promise
错误。
- 同步错误:可以通过
以上是我们了解WEB
错误捕获系统的基础知识体系。
借此我们可以大致明白市面上的监控系统的捕获动作是如何运行的,例如:Sentry、贝壳 LightHouse、友盟、阿里云 ARMS、腾讯 QMPM、OneAPM等都是极其优秀的监控系统。