前端错误的分类

系统异常

系统异常比较少,相关可能为浏览器奔溃

网络异常

  • XMLHttpRequest 请求异常
  • Fetch 请求异常
  • 静态资源加载异常

    应用异常

  • Error:错误的基类,其他错误都继承自该类型。

  • EvalError : 与 eval() 有关的错误。
  • RangeError : 表示这个值不在允许值集或范围内。
  • ReferenceError : 表示发现一个无效的引用。
  • SyntaxError : 表示发生了解析错误。
  • TypeError :当其它类型错误都不符合时,TypeError 用于指示一个不成功的操作。
  • URIError :表示用于处理 URI 的函数(encodeURI 或 decodeURl)使用方式与其定义的不兼容。
  • 异常参考:Top 10 JavaScript errors from 1000+ projects

    异常捕获

    try/catch/finally

    ```javascript try { var a = 1; var b = a + c; } catch (error) { // 捕获处理 console.log(error); // ReferenceError: c is not defined logger.error(“catch”, error); } finally { throw new Error(“error”); }
  1. **思考: 如果 catch 块和 finally 块都抛出异常,catch 块的异常是否能抛出?**<br />当该 `finally` 块引发异常时,它将有效地隐藏从该块引发的异常,并将 `catch` 最终引发该异常。因此,重要的是要么在捕获时记录异常,要么确保 `finally` 块本身不会引发异常。
  2. <a name="TFptk"></a>
  3. ## window.onerror
  4. > 请注意 `window.error` 无法捕获静态资源异常和 JS 代码错误。
  5. ```javascript
  6. /**
  7. * @param {String} message 错误信息
  8. * @param {String} source 错误文件路径
  9. * @param {Number} lineno 错误行号
  10. * @param {Number} colno 错误列号
  11. * @param {Object} error Error对象(对象)
  12. */
  13. window.onerror = function (message, source, lineno, colno, error) {
  14. console.log(`捕获到异常:${ message, source, lineno, colno, error }`);
  15. logger.error("oneror", JSON.stringify({ message, source, lineno, colno, error }));
  16. };

静态资源加载 异常

  1. <script>
  2. function errorHandler(error) {
  3. console.log(`捕获到静态资源加载异常: ${error}`);
  4. logger.error("onerror", JSON.stringify(error));
  5. }
  6. </script>
  7. <script src="http://cdn.xxx.com/js/test.js" onerror="errorHandler(this)"></script>
  8. <link rel="stylesheet" href="http://cdn.xxx.com/styles/index.css" onerror="errorHandler(this)">

Promise 异常

  1. window.addEventListener("unhandledrejection", event => {
  2. console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
  3. logger.error("promise", JSON.stringify(event));
  4. event.preventDefault();
  5. });
  6. // 或
  7. window.onunhandledrejection = event => {
  8. console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
  9. logger.error("promise", JSON.stringify(event));
  10. event.preventDefault();
  11. };

Vue

  1. /**
  2. * @name Vue 异常上报
  3. * @param {{message,name,script,line,column,stack}} err error 对象
  4. * @param {String} vm 抛出异常的 Vue 实例对象
  5. * @param {String} info Vue 特定的错误信息,比如错误所在的生命周期钩子
  6. */
  7. Vue.config.errorHandler = (err, vm, info) => {
  8. console.log("vue errorHandler", { err, vm, info });
  9. logger.error("vue error", JSON.stringify({ err, vm, info }));
  10. };

React

  1. import React from 'react'
  2. import { Modal } from 'antd'
  3. import './styles.scss'
  4. export default class ErrorBoundary extends React.Component {
  5. state = { hasError: false, error: null, tipText: null }
  6. componentDidCatch(error, info) {
  7. console.error(error, info)
  8. logger.error('react error', JSON.stringify({ error, info }))
  9. if (error && (error.toString().indexOf('ChunkLoadError') === 0 || error.toString().indexOf('Error: Loading CSS ') === 0)) {
  10. // 监测到Webpack异步模块加载失败
  11. Modal.confirm({
  12. title: '检测到网站可能有更新,需要刷新页面',
  13. okText: '刷新',
  14. cancelText: '关闭',
  15. maskClosable: false,
  16. onOk: () => {
  17. location.reload()
  18. },
  19. })
  20. this.setState({ hasError: true, tipText: '网站可能有更新,请刷新页面' })
  21. } else {
  22. this.setState({ hasError: true, error, tipText: null })
  23. }
  24. }
  25. render() {
  26. if (this.state.tipText) {
  27. return (
  28. <div className="page-error page-standard">
  29. <h2 className="title">{this.state.tipText}</h2>
  30. </div>
  31. )
  32. } else if (this.state.hasError) {
  33. return (
  34. <div className="page-error page-standard">
  35. <h2 className="title">抱歉,页面出错</h2>
  36. <h5 className="tip">请尝试刷新页面,或联系技术人员,以下是错误信息:</h5>
  37. <div className="error-message">{this.state.error ? this.state.error.toString() : '错误:未知错误'}</div>
  38. </div>
  39. )
  40. }
  41. return this.props.children
  42. }
  43. }
  44. // 使用案例:启动入口页面包裹
  45. import React from 'react'
  46. import { render } from 'react-dom'
  47. import ErrorBoundary from 'components/ErrorBoundary'
  48. const appRoot = document.getElementById('root')
  49. appRoot.setAttribute('notranslate', true)
  50. render(<ErrorBoundary>{/* code... */}</ErrorBoundary>, appRoot)

延伸: 跨域的 js 运行错误可以捕获吗,错误提示什么,应该怎么处理?
跨域之后 window.onerror 是无法捕获异常信息的,所以统一返回 Script error,解决方案

  1. 在 script 标签增加 crossorigin=”anonymous” 属性
  2. 设置 Access-Control-Allow-Origin: *

    监控平台搭建

    这个应该每个公司都有自己的监控平台,这里不列举过多。

如果数据量过大,架构需要对行为记录存储的设计与成本进行考虑。如容器存储与存放时间等

数据监控

  • PV:即页面浏览量或点击量
  • UV:指访问某个站点或点击某条新闻的不同IP地址的人数
  • 页面停留时长
  • 用户与数据来源
  • 触发行为

    性能监控

  • 不同环境下机型与系统下的首屏加载时间

  • DNS、TCP、request、页面渲染、load、加载、白屏等耗时时间

    异常监控

    JavaScript与样式异常

    埋点

    数据上报可以在延伸扩展:即时,批量,主动等上报方式,可根据业务优先级来决定

代码埋点与数据上报

异常与监控 - 图1