Skip to content

前端错误的分类

系统异常

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

网络异常

  • 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')
}

思考: 如果 catch 块和 finally 块都抛出异常,catch 块的异常是否能抛出? 当该 finally 块引发异常时,它将有效地隐藏从该块引发的异常,并将 catch 最终引发该异常。因此,重要的是要么在捕获时记录异常,要么确保 finally 块本身不会引发异常。

window.onerror

请注意 window.error 无法捕获静态资源异常和 JS 代码错误。

javascript
/**
 * @param {String}  message    错误信息
 * @param {String}  source     错误文件路径
 * @param {Number}  lineno     错误行号
 * @param {Number}  colno      错误列号
 * @param {Object}  error      Error对象(对象)
 */
window.onerror = function (message, source, lineno, colno, error) {
  console.log(`捕获到异常:${(message, source, lineno, colno, error)}`)
  logger.error('oneror', JSON.stringify({ message, source, lineno, colno, error }))
}

静态资源加载 异常

html
<script>
  function errorHandler(error) {
    console.log(`捕获到静态资源加载异常: ${error}`)
    logger.error('onerror', JSON.stringify(error))
  }
</script>

<script src="http://cdn.xxx.com/js/test.js" onerror="errorHandler(this)"></script>

<link
  rel="stylesheet"
  href="http://cdn.xxx.com/styles/index.css"
  onerror="errorHandler(this)"
/>

Promise 异常

javascript
window.addEventListener('unhandledrejection', (event) => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`)
  logger.error('promise', JSON.stringify(event))

  event.preventDefault()
})

// 或
window.onunhandledrejection = (event) => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`)
  logger.error('promise', JSON.stringify(event))

  event.preventDefault()
}

Vue

javascript
/**
 * @name Vue 异常上报
 * @param {{message,name,script,line,column,stack}} err error 对象
 * @param {String} vm 抛出异常的 Vue 实例对象
 * @param {String} info Vue 特定的错误信息,比如错误所在的生命周期钩子
 */
Vue.config.errorHandler = (err, vm, info) => {
  console.log('vue errorHandler', { err, vm, info })

  logger.error('vue error', JSON.stringify({ err, vm, info }))
}

React

javascript
import React from 'react'
import { Modal } from 'antd'
import './styles.scss'

export default class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null, tipText: null }

  componentDidCatch(error, info) {
    console.error(error, info)
    logger.error('react error', JSON.stringify({ error, info }))

    if (
      error &&
      (error.toString().indexOf('ChunkLoadError') === 0 ||
        error.toString().indexOf('Error: Loading CSS ') === 0)
    ) {
      // 监测到Webpack异步模块加载失败
      Modal.confirm({
        title: '检测到网站可能有更新,需要刷新页面',
        okText: '刷新',
        cancelText: '关闭',
        maskClosable: false,
        onOk: () => {
          location.reload()
        },
      })
      this.setState({ hasError: true, tipText: '网站可能有更新,请刷新页面' })
    } else {
      this.setState({ hasError: true, error, tipText: null })
    }
  }

  render() {
    if (this.state.tipText) {
      return (
        <div className='page-error page-standard'>
          <h2 className='title'>{this.state.tipText}</h2>
        </div>
      )
    } else if (this.state.hasError) {
      return (
        <div className='page-error page-standard'>
          <h2 className='title'>抱歉,页面出错</h2>
          <h5 className='tip'>请尝试刷新页面,或联系技术人员,以下是错误信息:</h5>
          <div className='error-message'>
            {this.state.error ? this.state.error.toString() : '错误:未知错误'}
          </div>
        </div>
      )
    }

    return this.props.children
  }
}

// 使用案例:启动入口页面包裹
import React from 'react'
import { render } from 'react-dom'
import ErrorBoundary from 'components/ErrorBoundary'

const appRoot = document.getElementById('root')
appRoot.setAttribute('notranslate', true)

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与样式异常

埋点

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

代码埋点与数据上报

diagram1.jpg