一、为什么要处理异常
错误处理是程序设计时必须要考虑的问题。
执行JavaScript代码的时候,有些情况下会发生错误。因为Js 是单线程,当脚本抛出了未捕获的异常会使得这个脚本挂起,后面的代码无法执行,所以很多情况需要捕获不致命的异常,让代码继续执行。
错误分两种,一种是程序写的逻辑不对,导致代码执行异常。这类错误在项目引入typescript之后即可检查出来,所以项目建议尽量引入typescript以规避一些基础的问题。
另一种,是执行过程中,程序可能遇到无法预测的异常情况而报错,例如,接口请求异常,网络异常,无权限等。
捕获异常,并上报异常监控平台,以及合理的异常处理,不仅能提升用户体验,也可使技术及时发现错误,并帮助分析、排查、跟踪和解决问题。
对于这些错误,我们需要做如下处理:
-
捕获:捕获异常。
-
处理:
- 技术侧评估,错误是否是致命的,会不会导致其它连带错误,是否需要做后续处理
- 技术侧:一般错误都需要上报到异常监控平台(常用的异常上报监控平台如Sentry),以便技术发现、排查和跟踪问题
- 和产品确认:是不是需要将错误信息反馈给用户,提示用户如何处理该错误:如接口超时等
二、需要处理哪些异常
-
异步任务异常
- Promise异常
- 接口请求异常:fetch,axios等,服务端报错,请求超时等
-
js语法错误,代码异常
-
静态资源加载异常:主要有图片、script、css、font等资源的加载错误问题
-
崩溃卡顿
三、如何捕获异常
建议接入Sentry上报系统,做异常监控处理。
四、常见的异常及其处理
Promise异常处理
在 promise
中使用 catch
可以非常方便的捕获到异步 error
。或者使用监听 unhandledrejection 来全局捕获这类异常。
注意:Promise 中的异常不能被 try-catch 和 window.onerror 捕获!(易错点!)
原因是,Promise 在执行回调中都用 try catch 包裹起来了,其中所有的异常都被内部捕获到了,并未往上抛异常。
javascript
// 方法一:
new Promise((resolve, reject) => {
reject('jartto: promise error');
}).catch(e=>{
console.log(e);
// 异常上报等异常处理
});
// 方法二:
promiseObj.then(undefined, (err)=>{
catch_statements
});
// 错误示范: Promise异常无法被try catch捕获
async function run() {
try {
Promise.reject(new Error("Oops1!"));
} catch (error) {
error.message;
}
}
run();
为了防止有漏掉的 Promise
异常,建议在全局增加一个对 unhandledrejection
的兜底监听,用来全局监听Uncaught Promise Error
- 注意: 使用Sentry上报时,Sentry会自行收集此类异常。如果自己再全局上报一次会导致重复上报。这里如果需要搜集额外信息,注意区分一下,否则可以直接用Sentry默认的上报即可。
javascript
window.addEventListener("unhandledrejection", function(e){
console.log(e);
// 异常上报等异常处理
});
接口请求异常
现在接口请求一般都用fetch或axios。可以分别:
- fetch拦截
- axios请求/响应拦截器
- 使用async/await + try catch结合的方式, 或者使用promise.catch
try- catch
能被 try catch 捕捉到的异常,必须是在报错的时候,线程执行已经进入 try catch 代码块,且处在 try catch 里面,这个时候才能被捕捉到。 如果是在之前,或者之后,都无法捕捉异常。
所以try catch能捕获捉到运行时非异步错误,无法捕获语法错误和异步错误。适用于命令式代码,不适用于声明式如React等(React捕获异常后续会说明)。
try catch 不能捕获的js异常:
- 语法错误
- 普通异步任务如setTimeout
- Promise任务
- async任务需要await才能捕获
例子:
csharp
try {
try_statements
}
[catch (exception) {
catch_statements
}]
[finally {
finally_statements
}]
全局js异常
方法一: window.onerror
监听 js 运行时错误事件,无法捕获资源加载异常。
window.onerror 捕获异常能力比 try-catch 稍微强点,无论是异步还是非异步错误,onerror 都能捕获到运行时错误。
ini
window.onerror = function (message, source, lineno, colno, error) { }
不能捕获的异常:
- 能捕获所有同步执行错误。
- 能捕获普通异步任务错误。
- 不能捕获语法错误。
- 不能捕获Promise任务错误。
- 不能捕获async任务错误。
- 不能捕获资源加载错误。
方法二: window.addEventListener('error')
监听 js 运行时错误事件,可以捕获资源加载异常。但错误堆栈不完整。
javascript
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
异步代码
使用async/await + try/catch
这里也可以使用相关babel插件,来给所有的async函数统一添加try/catch。感兴趣的同学可以了解下。
javascript
async function test() {
try {
const res = await requestWithTimeout(fakeAPI(), 0);
} catch (e) {
console.log("~~~e", e.message);
// 异常
}
}
iframe异常
分三种情况:
- iframe 页面和你的主站是同域名:直接给 iframe 添加 onerror 事件即可。
- iframe 页面和你的主站不是同个域名,但是自己可以控制,可以通过与 iframe 通信的方式将异常信息抛给主站接收。与 iframe 通信的方式有很多,常用的如:postMessage,hash 或者 name 字段跨域等
- iframe 页面和你的主站不是同个域名,且网站不受自己控制为第三方的话,没办法捕获,这是出于安全性的考虑,只能通过控制台看到详细的错误信息
静态资源加载异常
方法一:资源标签onerror 属性来捕获
ini
function errorHandler(error) {
console.log("捕获到静态资源加载异常", error);
}
<script src="http://**/js/test.js" onerror="errorHandler(this)"></script>
<link rel="stylesheet" href="http://***/test.css" onerror="errorHandler(this)"/>
这样可以拿到静态资源的错误,但缺点很明显,代码的侵入性太强了,每一个静态资源标签都要加上 onerror 方法。
是否也可以通过 window.onerror 去全局监听加载失败呢?答案是否定的。因为 onerror 的事件并不会向上冒泡,window.onerror 接收不到加载失败的错误。只能通过该资源标签的onerror 方法,才可以。
冒泡不行,但是捕获阶段可以,也即addEventListener("error"),下面的方法二。
方法二:addEventListener("error")
我们可以通过捕获的方式全局监控加载失败的错误,虽然这也监控到了脚本错误,但通过 !(event instanceof ErrorEvent) 判断便可以筛选出加载失败的错误
javascript
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
页面崩溃异常
javascript
window.addEventListener('load',()=>{
sessionStorage.setTitem('page_exit','pending')
})
window.addEventListener('beforeunload',()=>{
sessionStorage.setTitem('page_exit','true')
})
sessionStorage.getTitem('page_exit')!='true' // 页面崩溃
框架的异常处理
vue全局异常处理
主站已经加了,所以这里不用担心。
javascript
Vue.config.errorHandler = (err, vm, info) => {
console.error('通过vue errorHandler捕获的错误');
console.error(err);
console.error(vm);
console.error(info);
}
React异常处理
React 16 提供了一个内置函数 componentDidCatch,使用它可以非常简单的获取到 react 下的错误信息
javascript
componentDidCatch(error, info) {
console.log(error, info);
}
Error Boundary
UI
的某部分引起的 JS
错误不应该拖垮整个应用,React 16
引入了一种关于错误边界(error boundary
)的理念。
注意:error boundaries 不会捕捉下面这些错误:
- .事件处理器:如按钮点击事件等
- 异步代码
- 服务端的渲染代码
- 在
error boundaries
区域内的错误
## 不同环境的异常处理
Node中:
dart
process.on('uncaughtException', () => {})`