代码是很难真正意义的彻底按照开发者的想法运行的,意外状况老是层出不穷,听任无论显然不是一个合格的开发者该作的事情,错误信息该如何进行处理、收集以及分析显得尤其重要,这篇文章就对于这部份内容进行讨论。javascript
那对于前端同窗来讲,错误每每会阻塞程序运行,并抛出一个错误,给用户极其很差的体验。若是咱们能够提早对错误有所准备,将错误捕获作出反应,给用户更好的体验。也能够经过对错误信息的收集和分析,主动的去发现一些潜藏着的代码问题,不用等着用户绕一大个圈子来向你提bug,你就可以第一时间拿到各类信息。css
window.onerror会全局的在JavaScript运行时错误、语法错误发生时触发。html
window.onerror = (msg, url, lineNum, colNum, err) => {
console.log(`错误发生的异常信息(字符串):${msg}`)
console.log(`错误发生的脚本URL(字符串):${url}`)
console.log(`错误发生的行号(数字):${lineNum}`)
console.log(`错误发生的列号(数字):${colNum}`)
console.log(`错误发生的Error对象(错误对象):${err}`)
};
复制代码
注意:这里咱们能够拿到的是被throw出来,没有被catch过的错误。而不能拿到promise这样的错误。前端
凡事不会一路顺风,不少同窗再尝试的时候,必定发现了本身只能拿到一个Script error并无错误自己的message、url等信息,在lineNum和colNum也都是0,并非真正错误发生时的错误信息。java
缘由是浏览器在同源策略限制下所产生的。浏览器出于安全上的考虑,当页面引用的非同域的外部脚本中抛出了异常,此时本页面无权限得到这个异常详情, 将输出 Script error 的错误信息。在Chrome中有这样的安全机制,他不会将完整的跨域错误信息暴露给你,只在chrome中会出现这样的状况,在Firefox,Safari中都可以正常的拿到完整的错误信息。chrome
若是要解决这个问题,可使用跨源资源共享机制( CORS )数据库
<!-- 增长 crossorigin 属性后,浏览器将自动在请求头中添加一个 Origin 字段,告诉服务器本身的来源,服务器再判断是否返回 -->
<script src="http://xxx.xxx.xxx.x/xxx.js" crossorigin></script>
复制代码
你们能够根据本身的需求来判断是否须要处理这个问题,收集到这一部分不完整的错误信息。跨域
在前文中提到Promise中的错误并不能被try...catch和window.onerror捕获。这时候咱们就须要unhandledrejection来帮咱们捕获这部分错误。promise
window.addEventListener('unhandledrejection', (e) => {
console.log(`Promise.reject()中的内容,告诉你发生错误的缘由:${e.reason}`);
console.log(`Promise对象 :${e.promise}`);
});
复制代码
值得一提的是unhandledrejection的兼容性不是很好,下面附上一张caniuse的图浏览器
console.error经常被视为打印的日志,可预知的错误,已经被捕获的错误,已经被处理过的内容。因此每每会被忽视不去处理。
下面这样的代码老是很常见,作了不少事情,用一个大大的try...catch,将异常捕获而后打一个console.error完事,可能对于异常处理这样已经完事,捕获住了错误,没有让程序崩溃,但若是对于错误收集这也是不可缺乏的一部分
try {
// some code
} catch (err) {
console.error(err)
}
复制代码
因此稍稍改造一下console.error,让每一次触发console.error的时候咱们能够作一些事情,例如对错误收集系统作一下上报什么的。
console.error = (func => {
return (...args) => {
// 在这里就能够收集到console.error的错误
// 作一些事情
func.apply(console, args);
}
})(console.error);
复制代码
有大佬一眼指出我这一块的不足,下来学习了一下,把这一块内容补充上去。感谢@Dikaplio 🙏
在客户端方面,一些静态资源错误,图片呀,css呀,script呀,加载失败了。前面提到的方法都是没法捕获的。
<script src="https://cdn.xxx.com/js/test.js" onerror="errorHandler(this)"></script>
<link rel="stylesheet" href="https://cdn.xxx.com/styles/test.css" onerror="errorHandler(this)">
复制代码
这样就能够拿到这些静态资源的错误,可是呢,缺点也一样很明显,对代码的侵入型强了一些,不是一个好的办法。
在大多数状况下addEventListener('error')和window.onerror的效果差很少。在浏览器中有两种事件机制,捕获和冒泡,这两个方法就分别是经过捕获和冒泡来拿到error的。
可是对于资源的加载错误事件中,canBubble: false,因此理所应当的window.onerror是拿不到资源加载错误的,而addEventListener则能够拿到错误。可是在拿到错误之后须要简单的区分一下是资源加载错误仍是其余错误,由于该方法也可以捕获语法错误等一系列其余错误。
方法也很简单,他们之间有一个很明显的区别,其余的普通错误会有一个message字段,资源加载错误没有这个字段,这样只要让这一段代码运行在全部资源以前,那就能够拿到这方面的错误了。
window.addEventListener('error', (errorEvent) => {
console.log(errorEvent)
cosnole.log(errorEvent.message)
}, true)
复制代码
须要注意的是这里拿到的是一个event事件,和前面不同,拿到的并非一个error对象。
在Node服务端的收集其实和客户端上大同小异,只是一些方法上的区别.
经过Node的全局处理,捕获全部未被处理的错误,这是最后一层关卡,兜底的操做,若是还不处理的话每每会致使程序崩溃。
process.on('uncaughtException', err => {
//do something
});
复制代码
在Node中,Promise中的错误一样不能被try...catch和uncaughtException捕获。这时候咱们就须要unhandledRejection来帮咱们捕获这部分错误。
process.on('unhandledRejection', err => {
//do something
});
复制代码
console.error = (func => {
return (...args) => {
// 在这里就能够收集到console.error的错误
// 作一些事情
func.apply(console, args);
}
})(console.error);
复制代码
对于Node端咱们每每,能够借助框架对错误进行捕获,像koa就能够经过app.on error对错误在框架这一层进行捕获,一样他也是捕获内部没有被catch到的错误,像promise错误并不能捕获。
app.on('error', (err, ctx) => {
// do something
});
复制代码
值得一提的是,咱们能够在框架内部主动的触发这个error事件,对即便已经被咱们捕获了处理过的错误,也继续抛到框架这一层来,方便作不少统一处理。
ctx.app.emit('error', err, ctx);
复制代码
同步错误 => 能够被1.try...catch 2.window.onerror 3.process.on('uncaughtException')捕获。
异步错误 => 例如setInterval、没有被await的异步函数等,是不会被try...catch捕获的,可是会被window.onerror和process.on('uncaughtException')捕获。
Promise错误 => Promise.reject(new Error('some wrong'));像是这样的promise错误,是不会被window.onerror和process.on('uncaughtException')捕获的,更不会被try...catch捕获,想要捕获它们只能,process.on('unhandledRejection')以及window.addEventListener('unhandledrejection')
注意:在局部被try...catch了的错误是不会继续往上层抛出了的,因此全局处理的捕获是确定捕获不到的,除非在catch到之后处理完成,将错误继续向上层throw。
总体思路: 在业务层对错误捕获包装后继续向上层抛出,在包装中的时候,将全部的错误都继承自咱们本身定义的错误类,在错误类中有不少咱们自定义好的错误类型,在抛出的时候只须要简单的抛一下这个错误类型的实例就好,在最后中间件的时候咱们能够catch到所有的错误作统一的处理。这时的错误是被分过类,分过级的,还有一部分多是以前从未被捕获的,在这就能够干不少事了。
class SystemError extends Error {
constructor(message) {
super(message);
// 错误类型
// 错误等级
// 错误信息
// ...
}
static wrapper(e) {
const error = new this(e.message);
// 将e上的各类东西包装到error上
return error;
}
}
//能够对常见的错误提早定义好
createDBError(xxx) {
const sysError = SystemError.wrapper(error);
// 写入错误信息
// 写入错误类型
// 写入错误等级
// ...
return sysError;
}
//这样在业务中抛错的时候只须要简单的
throw createDBError(error, { someInfo });
复制代码
在业务中尽量精确的捕获错误,根据错误,进行定级,分类等操做,而后继续向上层抛出。
由于要精确的捕获错误,很容易形成大量try...catch嵌套的的状况,咱们要尽量的避免这样臃肿的代码
try {
try {
// 操做数据库
} catch (err) {
throw createDBError(error, { someInfo });
}
try {
// 正常业务
} catch (err) {
throw createBusinessError(error, { someInfo });
}
} catch (err) {
throw err
}
复制代码
这时候必定是咱们的代码有问题了,这时候咱们就要想是否是能够拆分开来,不会形成这样臃肿的局面。
由于前面全部的错误咱们都只作了包装,而且继续上报,因此在最上层的中间件中,咱们能够对全部的错误进行统一处理。
和各类错误打了一段时间交道,把本身的收获分享出来,但愿你们之后在异常处理的时候能够更驾轻就熟。