什么是异常
是指用户在使用应用时,无法得到预期的结果。不同的异常带来的后果程度不同,轻则引起用户使用不悦,重则导致产品无法使用,从而使用户丧失对产品的认可。
为什么要处理异常
增强用户体验
远程定位问题
无法复现问题,特别是移动端,各种原因,可能是系统版本,机型等等
前端有哪些异常
异常
频率
JavaScript 异常(语法错误、代码错误)
经常
静态资源加载异常(img、js、css)
偶尔
Ajax 请求异常
偶尔
承诺异常
较少
iframe 异常
较少
如何捕获异常
尝试捕获
try-catch只能捕获同步运行错误,对语法和异步错误却捕获不到。
1、同步运行错误
try { kill; } catch(err) { console.error('try: ', err); }
结果:try: ReferenceError: kill is not defined
2、无法捕获语法错误
try { let name = '1; } catch(err) { console.error('try: ', err); }
结果:Unterminated string constant
编译器能够阻止运行语法错误。
3、无法捕获异步错误
try { setTimeout(() => { undefined.map(v => v); }, 1000); } catch(err) { console.error('try: ', err); }
结果:Uncaught TypeError: Cannot read property 'map' of undefined
窗口错误
当 运行时错误(包括语法错误)发生时, 会触发一个 接口的 事件,并执行 若该函数返回 ,则阻止执行默认事件处理函数。JavaScriptwindowErrorEventerrorwindow.onerror()true
1、同步运行错误
/** * @param {String} message 错误信息 * @param {String} source 出错文件 * @param {Number} lineno 行号 * @param {Number} colno 列号 * @param {Object} error error对象 */ window.onerror = (message, source, lineno, colno, error) => { console.error('捕获异常:', message, source, lineno, colno, error); return true; }; kill;
结果:捕获异常: Uncaught ReferenceError: kill is not defined
2、无法捕获语法错误
/** * @param {String} message 错误信息 * @param {String} source 出错文件 * @param {Number} lineno 行号 * @param {Number} colno 列号 * @param {Object} error error对象 */ window.onerror = (message, source, lineno, colno, error) => { console.error('捕获异常:', message, source, lineno, colno, error); return true; }; let name = '1;
结果:Unterminated string constant
编译器能够阻止运行语法错误。
3、异步错误
/** * @param {String} message 错误信息 * @param {String} source 出错文件 * @param {Number} lineno 行号 * @param {Number} colno 列号 * @param {Object} error error对象 */ window.onerror = (message, source, lineno, colno, error) => { console.error('捕获异常:', message, source, lineno, colno, error); return true; }; setTimeout(() => { undefined.map(v => v); }, 1000);
结果:捕获异常: Uncaught TypeError: Cannot read property 'map' of undefined`
window.addEventListener('error')
当一项资源(如 或 )加载失败,加载资源的元素会触发一个 接口的 事件,并执行该元素上的 处理函数。这些 事件不会向上冒泡到 ,不过(至少在 Firefox 中)能被单一的 . 捕获。

结果:捕获异常:Event {isTrusted: true, type: "error", target: img, currentTarget: Window, eventPhase: 1, …}
window.addEventListener('unhandledrejection')
当 被 且没有 处理器的时候,会触发 事件;这可能发生在 下,但也可能发生在 中。 这对于调试回退错误处理非常有用。PromiserejectrejectunhandledrejectionwindowWorker
window.addEventListener("unhandledrejection", (err) => { err.preventDefault(); console.error('捕获异常:', err); }); Promise.reject('promise');
结果:捕获异常:PromiseRejectionEvent {isTrusted: true, promise: Promise, reason: "promise", type: "unhandledrejection", target: Window, …}
Vue
Vue.config.errorHandler = (err, vm, info) => { console.error('捕获异常:', err, vm, info); }
React
React16,提供了一个内置函数 ,使用它可以非常简单的获取到 下的错误信息。componentDidCatchReact
componentDidCatch(error, info) { console.error('捕获异常:', error, info); }
但是,推荐ErrorBoundary
用户界面中的 错误不应破坏整个应用程序。为了为 用户解决此问题, 引入了“错误边界”的新概念。JavaScriptReactReact16
新建 组件:ErrorBoundary.jsx
import React from 'react'; import { Result, Button } from 'antd'; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, info: '' }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, info) { this.setState({ info: error + '' }); } render() { if (this.state.hasError) { // 你可以渲染任何自定义的降级 UI return (
Report feedback} /> ); } return this.props.children; } } export default ErrorBoundary;
使用:
注意
错误边界不会捕获以下方面的错误:
事件处理程序
异步代码(例如 或 回调)setTimeoutrequestAnimationFrame
服务器端渲染
在错误边界本身(而不是其子级)中引发的错误
iframe
由于浏览器设置的“同源策略”,无法非常优雅的处理 异常,除了基本属性(例如其宽度和高度)之外,无法从 获得很多信息。iframeiframe
哨兵
业界非常优秀的一款监控异常的产品,作者也是用的这款,文档齐全。
需要上报哪些信息
错误 id
用户 ID
用户名
用户 IP
设备
错误信息
游览器
系统版本
应用版本
机型
时间戳
异常级别(错误、警告、信息)
异常上报
1、Ajax 发送数据
2、动态创建 img 标签
如果异常数据量大,导致服务器负载高,调整发送频率(可以考虑把异常信息存储在客户端,设定时间阀值,进行上报)或设置采集率(采集率应该通过实际情况来设定,随机数,或者某些用户特征都是不错的选择)。
流程图
参考资料
如何优雅处理前端异常?
反应
多核
Vue