前言
随着前端监控体系的日益完善,前端工程师们也对异常更加地关注,大家也更重视异常捕获与消息上报,但是产生的原因以及处理办法所谈甚少,那么前端异常到底有哪些呢?
什么是异常
异常:Exception
,即预料之外的事情,在程序执行的时候会打断程序正常运行。
有哪些
ECMA-262
白皮书 13 版中描述了 8 种异常:
- SyntaxError:语法异常
- ReferenceError:引用异常
- RangeError:范围异常
- Error:异常基类
- InternalError:内部异常
- TypeError: 类型异常
- EvalError: Eval 方法异常
- URIError: URI 相关方法产生的异常
SyntaxError
在引擎执行代码之前,编译器需要对 js 进行编译,编辑阶段包括:词法分析,语法分析。
编译阶段发生的异常都是 SyntaxError,但 SyntaxError 不完全都发生于编译阶段;
常见的 SyntaxError:
- SyntaxError: Invalid or unexpected token
- SyntaxError:Unexpected token u in JSON at position 0
- SyntaxError:Unexpected token ‘<’
- SyntaxError:Unexpected identifier
ReferenceError
引用异常,比较常见,类似于 Java 语言中最著名的空指针异常 (Null Pointer Exception,NPE).
常见的有:
ReferenceError:$ is not defined
ReferenceError:Can't find variable: $
TypeError
TypeError 在对值进行不合理操作时会发生,比如试图对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的值中的属性,那么引擎会抛出这种类型的异常。
常见的有:
TypeError:Cannot read property 'length' of undefined
RangeError
范围错误,比如:
- new Array(-20) 会导致
RangeError: Invalid array length
- 递归等消耗内存的程序会导致
RangeError: Maximum call stack size exceeded
Error 与自定义异常
Error 是所有错误的基类,其他错误类型继承该类型。所有错误类型都共享相同的属性。
Error.prototype.message
错误消息。对于用户创建的 Error 对象,这是构造函数的第一个参数提供的字符串Error.prototype.name
错误名称。这是由构造函数决定的Error.prototype.stack
错误堆栈
Error: Script Error
它是 Error 类型中最常见的一种;由于没有具体异常堆栈和代码行列号,成为可最神秘的异常之一。
由于浏览器基于安全考虑效避免敏感信息无意中被第三方 (不受控制的) 脚本捕获到,浏览器只允许同域下的脚本捕获具体的错误信息。
但大部分的 JS 文件都存放在 CDN 上面,跟页面的域名不一致。做异常监控只能捕获 Error: Script Error. 无法捕获堆栈和准确的信息。
其他异常
InternalError
这种异常极为少见,在 JS 引擎内部发生,示例场景通常为某些成分过大,例如:
too many switch cases
(过多 case 子句)too many parentheses in regular expression
(正则表达式中括号过多)array initializer too large
(数组初始化器过大)
EvalError
在 eval() 方法执行过程中抛出 EvalError 异常
URIError
用来表示以一种错误的方式使用全局 URI 处理函数而产生的错误.decodeURI, decodeURIComponent, encodeURI, encodeURIComponent
这四个方法会产生这种异常;
比如执行 decodeURI('%%')
的异常:Uncaught URIError: URI malformed
.
异常处理
finally
finally 在 try-catch 语句中是可选的,finally 子句一经使用,其代码无论如何都会执行。
throw
throw new Error('Boom');
什么时候应该手动抛出异常呢?
一个指导原则就是可预测程序在某种情况下不能正确进行下去,需要告诉调用者异常的详细信息,而不仅仅是异常内容本身。
assert
断言,如果表达式不符合预期,就抛出一个错误。
assert 方法接受两个参数,当第一个参数对应的布尔值为 true 时,不会有任何提示,返回 undefined。当第一个参数对应的布尔值为 false 时,会抛出一个错误,该错误的提示信息就是第二个参数设定的字符串。
异步中的异常
非同步的代码,在事件循环中执行的,就无法通过 try catch 到。
主要注意的是,Promise 的 catch 方法用于处理 rejected 状态,而非处理异常。Rejected 状态未处理的话会触发 Uncaught Rejection. 后者可以通过如下方式进行统一的监听。
window.onunhandledrejection = (event) => {
console.warn(`REJECTION: ${event.reason}`);
};
tips: await 这种 Promise 的同步写法,通常会被开发者忽略 rejected 的处理,可以用 try catch 来捕获。
异常监控
服务端通常会通过服务器的日志进行异常监控,比如观察单台服务器的日志输出,或 kibana 可视化查询。
前端异常监控与之最大的不同,就是需要把客户端发生的异常数据通过网络再收集起来。
参考: