Abnormal processing front end

First, why should handle exceptions?

The exception is not controlled, it will affect the presentation of the final results, but we have every reason to do such a thing.

  1. Enhance the user experience;
  2. Remote location problem;
  3. Rainy day, early detection of problems;
  4. Not double-track problems, especially mobile terminal models, the system is the problem;
  5. Complete front-end solutions, front-end monitoring system;

For JS, just unusual, abnormal appearance we face does not directly lead to the collapse of JS engine, it will only make the task currently executing is terminated.

Second, the need to deal with which exceptions?

For the front, we can do exception caught quite a bit. To sum up, probably as follows:

  • JS syntax error codes Exception
  • AJAX request an exception
  • Abnormal Load static resources
  • Promise abnormal
  • Iframe abnormal
  • Cross-domain Script error
  • Collapse and Caton

Now I will for each particular case to illustrate how to handle these exceptions.

Three, Try-Catch Mistakes

try-catch only captures the synchronous run-time error, syntax and asynchronous errors can not do anything, not capture.

  1. Synchronous run-time error:
{the try 
the let name = 'jartto';
the console.log (Nam);
} the catch (E) {
the console.log ( 'capture exception:', E);
}

Output:

Exception caught: a ReferenceError: Not defined Nam IS 
AT <Anonymous>: . 3: 15
  1. You can not capture the specific syntax error, only a syntax error. We modify the code, delete a single quote:
{the try 
the let name = 'jartto;
the console.log (Nam);
} the catch (E) {

the console.log (' capture exception: ', E);
}

Output:

Uncaught SyntaxError: Unexpected token Invalid or 
however syntax errors can be seen at our stage of development, we should not be successful on the online environment.
  1. Asynchronous error
{the try 
the setTimeout ( () => {
undefined.map ( V => V);
}, 1000)
} the catch (E) {
the console.log ( 'capture exception:', E);
}

We look at the log:

Uncaught TypeError: Cannot read property 'map' of undefined
at setTimeout (<anonymous>:3:11)

Exception caught and no, this is where we need special attention.

Four, window.onerror is not a panacea

When an error occurs JS runtime, window will trigger an error event ErrorEvent interface, and perform window.onerror ().

/ ** 
* @param {String} error Message
* @param {String} source error file
* @param {Number} lineno line number
* @param {Number} colno column number
* @param {Object} error Error Object (Object ) *
/

window.onerror = function ( Message, Source, lineno, colno, error) {
the console.log ( 'capture exception:', {Message, Source, lineno, colno, error});
}
  1. First try synchronous run-time errors
= window.onerror function ( Message, Source, lineno, colno, error) { 
// Message: Error message (character string).
// source: the error occurred script URL (string)
// lineno: error line number (numeric)
// colno: column number where the error occurred (numeric)
// error: Error objects (objects)
console.log ( 'exception caught:', {Message, Source, lineno, colno, error});
}
Jartto;

You can see, we captured exception:

  1. Try again grammar mistakes?
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
let name = 'Jartto

Console print out this exception:

Uncaught SyntaxError: Invalid or unexpected token

What, not even capture the grammatical errors?

  1. With disturbed mind, we finally try to asynchronous run-time error:
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
setTimeout(() => {
Jartto;
});

Console output:

捕获到异常:{message: "Uncaught ReferenceError: Jartto is not defined", source: "http://127.0.0.1:8001/", lineno: 36, colno: 5, error: ReferenceError: Jartto is not defined
at setTimeout (http://127.0.0.1:8001/:36:5)}
  1. Then, we try unusual request the network:
<script>
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
return true;
}
</script>
<img src="./jartto.png">

我们发现,不论是静态资源异常,或者接口异常,错误都无法捕获到。Add that: window.onerror function returns true when only exception will not throw up, or even be aware of the occurrence of abnormal console will still show Uncaught Error: xxxxx

window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
return true;
}
setTimeout(() => {
Jartto;
});

The console will not have such a mistake:

Uncaught ReferenceError: Jartto is not defined
at setTimeout ((index):36)

requires attention:

  • onerror best to write in front of all the JS script, or they may not catch errors;
  • onerror not catch grammatical errors;

Here basically clear: in the actual use of the process, mainly to capture the onerror unexpected error, try-catch and is used to monitor specific errors under foreseeable circumstances, more efficient use of a combination of both. Problem again, capturing less than the static resource loading abnormal how to do?

五、window.addEventListener

When a resource (such as images or scripts) fails to load, load the resource element triggers an error event Event interface, and perform onerror on the element () handler. These error events do not bubble up to the window, but (at least in Firefox) can be a single window.addEventListener capture.

<scritp>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<img src="./jartto.png">

Console output: Due to abnormal network requests will not event bubbling, and therefore must be captured during the capture phase to the job, but in this way, although you can capture the abnormal network requests, but can not determine the status of HTTP is 404 or other such 500 and so on, so it needs to fit the server log analysis can only carry out the investigation. requires attention:

  • error object returned may be different in different browsers, need to pay attention compatible processing.
  • AddEventListener need to be taken to avoid duplicate monitoring.

六、Promise Catch

Use catch in the promise can be very convenient to capture asynchronous error, this is very simple. Promise not to write catch the thrown error can not be caught or onerror to try-catch, so we want to be sure not to forget to catch exceptions thrown in the treatment of Promise. Solution: In order to prevent abnormal missed Promise, a proposed increase in the global monitor of unhandledrejection, to globally monitor Uncaught Promise Error. Use:

window.addEventListener("unhandledrejection", function(e){
console.log(e);
});

We continue to try:

window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
Promise.reject('promise error');

You can see the following output: What if for Promise not catch it?

window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
new Promise((resolve, reject) => {
reject('jartto: promise error');
});

嗯,事实证明,也是会被正常捕获到的。所以,正如我们上面所说,为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听 Uncaught Promise Error。补充一点:如果去掉控制台的异常显示,需要加上:

event.preventDefault();

VUE errorHandler

Vue.config.errorHandler = (err, vm, info) => {
console.error('通过vue errorHandler捕获的错误');
console.error(err);
console.error(vm);
console.error(info);
}

八、React 异常捕获 React 16 提供了一个内置函数 componentDidCatch,使用它可以非常简单的获取到 react 下的错误信息

componentDidCatch(error, info) {
console.log(error, info);
}

除此之外,我们可以了解一下:error boundary UI 的某部分引起的 JS 错误不应该破坏整个程序,为了帮 React 的使用者解决这个问题,React 16 介绍了一种关于错误边界(error boundary)的新观念。需要注意的是:error boundaries 并不会捕捉下面这些错误。

  1. 事件处理器
  2. 异步代码
  3. 服务端的渲染代码
  4. 在 error boundaries 区域内的错误

我们来举一个小例子,在下面这个 componentDIdCatch(error,info) 里的类会变成一个 error boundary:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}

然后我们像使用普通组件那样使用它:

<ErrorBoundary>
<MyWidget />
</ErrorBoundary>

componentDidCatch() 方法像 JS 的 catch{} 模块一样工作,但是对于组件,只有 class 类型的组件(class component )可以成为一个 error boundaries 。实际上,大多数情况下我们可以在整个程序中定义一个 error boundary 组件,之后就可以一直使用它了!

九、iframe 异常

对于 iframe 的异常捕获,我们还得借力 window.onerror:

window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}

一个简单的例子可能如下:

<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
window.frames[0].onerror = function (message, source, lineno, colno, error) {
console.log('捕获到 iframe 异常:',{message, source, lineno, colno, error});
return true;
};
</script>

十、Script error

一般情况,如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性。

<script src="http://jartto.wang/main.js" crossorigin></script>

或者动态去添加 js 脚本:

const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);

特别注意,服务器端需要设置:Access-Control-Allow-Origin此外,我们也可以试试这个-解决 Script Error 的另类思路:

const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
const wrappedListener = function (...args) {
try {
return listener.apply(this, args);
}
catch (err) {
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}

简单解释一下:改写了 EventTarget 的 addEventListener 方法;对传入的 listener 进行包装,返回包装过的 listener,对其执行进行 try-catch;浏览器不会对 try-catch 起来的异常进行跨域拦截,所以 catch 到的时候,是有堆栈信息的;重新 throw 出来异常的时候,执行的是同域代码,所以 window.onerror 捕获的时候不会丢失堆栈信息;利用包装 addEventListener,我们还可以达到「扩展堆栈」的效果:

(() => {
const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
+ // 捕获添加事件时的堆栈
+ const addStack = new Error(`Event (${type})`).stack;
const wrappedListener = function (...args) {
try {
return listener.apply(this, args);
}
catch (err) {
+ // 异常发生时,扩展堆栈
+ err.stack += '\n' + addStack;
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}
})();

十一、崩溃和卡顿

卡顿也就是网页暂时响应比较慢, JS 可能无法及时执行。但崩溃就不一样了,网页都崩溃了,JS 都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢?崩溃和卡顿也是不可忽视的,也许会导致你的用户流失。

  1. 利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控。不错的文章,推荐阅读:Logging Information on Browser Crashes。
window.addEventListener('load', function () {
sessionStorage.setItem('good_exit', 'pending');
setInterval(function () {
sessionStorage.setItem('time_before_crash', new Date().toString());
}, 1000);
});

window.addEventListener('beforeunload', function () {
sessionStorage.setItem('good_exit', 'true');
});

if(sessionStorage.getItem('good_exit') &&
sessionStorage.getItem('good_exit') !== 'true') {
/*
insert crash logging code here
*/
alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
}
  1. 基于以下原因,我们可以使用 Service Worker 来实现网页崩溃的监控:

Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。

十二、错误上报

1.通过 Ajax 发送数据 因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。2.动态创建 img 标签的形式

function report(error) {
let reportUrl = 'http://jartto.wang/report';
new Image().src = `${reportUrl}?logs=${error}`;
}

收集异常信息量太多,怎么办?实际中,我们不得不考虑这样一种情况:如果你的网站访问量很大,那么一个必然的错误发送的信息就有很多条,这时候,我们需要设置采集率,从而减缓服务器的压力:

Reporter.send = function(data) {
// 只采集 30%
if(Math.random() < 0.3) {
send(data) // 上报错误信息
}
}

采集率应该通过实际情况来设定,随机数,或者某些用户特征都是不错的选择。

十三、总结

回到我们开头提出的那个问题,如何优雅的处理异常呢?

  1. 可疑区域增加 Try-Catch
  2. 全局监控 JS 异常 window.onerror
  3. 全局监控静态资源异常 window.addEventListener
  4. 捕获没有 Catch 的 Promise 异常:unhandledrejection
  5. VUE errorHandler 和 React componentDidCatch
  6. 监控网页崩溃:window 对象的 load 和 beforeunload
  7. 跨域 crossOrigin 解决

其实很简单,正如上文所说:采用组合方案,分类型的去捕获异常,这样基本 80%-90% 的问题都化于无形。

本文非原创,是摘抄自公众号:

作者: Jartto's blog

http://jartto.wang/2018/11/20/js-exception-handling/

十四、参考

  • Logging Information on Browser Crashes http://jasonjl.me/blog/2015/06/21/taking-action-on-browser-crashes/
  • 前端代码异常监控实战 https://github.com/happylindz/blog/issues/5
  • Error Boundaries https://blog.csdn.net/a986597353/article/details/78469979
  • 前端监控知识点 https://github.com/RicardoCao-Biker/Front-End-Monitoring/blob/master/BasicKnowledge.md
  • Capture and report JavaScript errors with window.onerror https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror

Guess you like

Origin www.cnblogs.com/zale-blogs/p/12154993.html