引子
在 前端异常类型及捕获方式 之后,尝试了自己去封装一下,然后去看了 sentry 的源码,发现之前的那篇只是一个概括,当采集的时候,需要更加细致的解析,接下来看看各种异常具体是什么样的。
基础知识点
前端异常一定会接触到 Error
对象,先来简单的了解一下。
错误的类型:
- Error : 基类型,其它错误类型都是继承自该类型。
- EvalError : 使用
eval()
函数而发生异常时抛出。 - RangeError : 数值超出相应有效范围时的异常。
- ReferenceError : 访问无效的引用的异常。
- SyntaxError : 语法错误的异常。
- TypeError : 变量或参数不是一个有效类型的异常。
- URIError :
encodeURI()
或decodeURI()
传入无效参数的异常。 - AggregateError : 一个操作导致多个异常需要报告,例如
Promise.any()
。 - InternalError : JavaScript 引擎内部抛出的错误,这个还没有标准化。
实例共有的标准属性:
- message : 异常信息。
- name : 异常名称。
实例共有的非标准属性:
- description : 微软的非标准属性,类似 message 。
- number : 微软的非标准属性,异常数字。
- fileName : Mozilla 的非标准属性,异常产生所在文件的路径。
- lineNumber : Mozilla 的非标准属性,异常产生所在文件的行数。
- columnNumber : Mozilla 的非标准属性,异常产生所在文件的列数。
- stack : Mozilla 的非标准属性,堆栈跟踪。
更多异常相关的信息见 ecma-262 Error 和 WebIDL Exceptions 。
下面看看每种类型的示例。(暂没有考虑框架)
以下示例环境:
- Chrome :86.0.4240.198(正式版本) (x86_64)。
- 使用 nginx 启动了一个本地服务,原生 js 。
- 为了方便查看更多信息,使用了 try-catch、onerror、onunhandledrejection 。
EvalError
EvalError
已不再被 JavaScript 抛出,现在该对象为了保持兼容性存在。用下面的方式可以看到这种异常:
try {
throw new EvalError('Hello, EvalError');
} catch (e) {
console.log(e instanceof EvalError); // true
console.log(e.message); // "Hello, EvalError"
console.log(e.name); // "EvalError"
}
查一些资料说下面方式就会抛出该类型异常,但试了一下抛出的异常是 TypeError
。
new eval();
RangeError
const arr = new Array(-10)
ReferenceError
let a = undefinedVariable
SyntaxError
eval('hello syntax')
有些语法错误是无法捕获的,因为可能导致整个程序无法正常运行,不过这类型大多在编写阶段就会很容易发现。
const a++;
TypeError
const a = 'hell';
a.fun();
URIError
decodeURIComponent('%')
AggregateError
Promise.any([
Promise.reject(new Error("some error")),
]).catch(e => {
throw e; // 这里抛出,让 onunhandledrejection 接收会有详细的异常信息
});
DOMException
const node = document.querySelector('#demo'); // 这个要存在
const refnode = node.nextSibling;
const newnode = document.createTextNode('异常');
node.insertBefore(newnode, refnode);
DOMError
已经不推荐使用了,但一些浏览器还是兼容了这个。
const err = new DOMError('DOMError');
throw err;
ErrorEvent
const err = new ErrorEvent('ErrorEvent');
throw err; // onerror 会捕获到
资源加载异常
常见的 link
、script
、img
标签加载异常都是这样类似的信息:
接口请求异常
基础 XMLHttpRequest
示例:
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:6677/index");
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.info('success')
} else {
console.info('xhr error:',xhr)
}
}
};
xhr.send();
基础 fetch
示例:
fetch("http://localhost:6677/index").then((res) => {
if (!res.ok) {
throw res;
}
}).catch((e) => {
console.info('fetch error:',e);
})
Script error.
这个本地(浏览器直接文件路径访问)很容易出现,例如点击按钮的时候,抛出了一个异常,onerror
也捕获到了,但什么信息也没有:
换成 try-catch
捕获,有了一些信息:
这个应该是一个对象,用对象的形式打印一下:
这样就可以拿到相关的信息进行解析了,可参考 TraceKit 。
区分
看到上面的示例,有好几种形式的异常,进行处理的时候,区分判断的依据是什么?
思路 1
比较简单的做法,就是不用区分,既然都是异常,整个全都上报,在后台人工查看一样可以达到异常分析排查的目的。
思路 2
通过检查特定字段,或者字段的组合来判断区分。针对特定的异常可以做到,所有的就不太清楚了。
思路 3
在看 sentry 源码的时候,发现了另外一种思路:根据异常的类型来区分。
使用的主要方法是:
function getType(value) {
return Object.prototype.toString.call(value);
}
按照这个思路,异常类型有:
- [object Error] : 属于这种类型的异常有
RangeError
、ReferenceError
、SyntaxError
、TypeError
、URIError
。 - [object Exception] : 这个没找到,但 sentry 里面有写。
- [object DOMException] : 属于这种类型的异常有
DOMException
。 - [object DOMError] : 属于这种类型的异常有
DOMError
。 - [object ErrorEvent] : 属于这种类型的异常有
ErrorEvent
。 - [object PromiseRejectionEvent] : 这个有些特殊,当上面的 AggregateError 在
catch
里面检查类型时属于[object Error]
,如果throw
时,在onunhandledrejection
里面类型变成了[object PromiseRejectionEvent]
。
这里大概的写一下 sentry 的区分逻辑:
const exception = 'some value';
let format;
if (isErrorEvent(exception) && exception.error) {
format = doSomething1(exception);
return format;
}
if (isDOMError(exception) || isDOMException(exception)) {
format = doSomething2(exception);
return format;
}
// isError 包含的类型有 [object Error] [object Exception] [object DOMException] ,以及继承自 Error 对象的自定义对象。
if (isError(exception)) {
format = doSomething3(exception);
return format;
}
// isPlainObject 检查类型是 [object Object],isEvent检查的是 wat instanceof Event
if (isPlainObject(exception) || isEvent(exception)) {
format = doSomething4(exception);
return format;
}
format = doSomething5(exception);
return format;