13

js错误捕获

js错误的实质,也是发出一个事件,处理他

error实例对象

  • 对象属性

    • message:错误提示信息
    • name:错误名称(非标准属性)宿主环境赋予
    • stack:错误的堆栈(非标准属性)宿主环境赋予
  • 对象类型(7种)

    • SyntaxError对象是解析代码时发生的语法错误
    • ReferenceError对象是引用一个不存在的变量时发生的错误
    • RangeError对象是一个值超出有效范围时发生的错误(一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值)
    • TypeError对象是变量或参数不是预期类型时发生的错误:对字符串、布尔值、数值等原始类型的值使用new命令
    • URIError对象是 URI 相关函数的参数不正确时抛出的错误:使用函数不当
    • eval函数没有被正确执行时,会抛出EvalError错误 - 不再使用,为了代码兼容
  • 自定义错误

    function UserError(message) {
      this.message = message || '默认信息';
      this.name = 'UserError';
    }
    
    UserError.prototype = new Error();
    UserError.prototype.constructor = UserError;
    new UserError('这是自定义的错误!');

Js运行时错误处理机制

  • try..catch…finally

    • 范围:用来捕获任何类型的同步错误,可以捕获async / await的代码,但无法捕获promise、setTimeout、dom回调(eg:onclick点击回调)的代码,在回调函数里面写try…catch可以捕获,但包在外面不会捕获,无法捕获语法错误
    • 异步不捕获原因:
    • async/await捕获原因:
  • window.onerror

    • 范围:同步错误和异步错误都可以捕获,但无法捕获到静态资源异常,或者接口异常(网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行),无法捕获语法错误
    • 原理:当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件
    • 参数

      /**
      * @param {String}  message    错误信息
      * @param {String}  source    出错文件
      * @param {Number}  lineno    行号
      * @param {Number}  colno    列号
*/
window.onerror = function(message, source, lineno, colno, error) {
   console.log('捕获到异常:',{message, source, lineno, colno, error});
}
```
  • 补充:window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx
  • onerror 最好写在所有 JS 脚本的前面,否则有可能捕获不到错误;(捕获的是全局错误)

资源加载错误

  • window.addEventListener(一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror() 处理函数,有浏览器兼容问题)
  • 注意:只能捕获无法冒泡

    window.addEventListener('error', (error) => {
        console.log('捕获到异常:', error);
    }, true) // 一定要加true,捕获但不冒泡
  • Script error跨域的静态资源加载异常捕获(cdn文件等)

    跨域文件只会报Script error,没有详细信息,怎么解决:

    • 客户端:script标签添加crossOrigin
    • 服务端:设置:Access-Control-Allow-Origin
    <script src="http://jartto.wang/main.js" crossorigin></script>

iframe异常

  • 使用window.onerror

    <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>

promise异常捕获

  • 没有写 catchPromise 中抛出的错误无法被 onerrortry-catch 捕获到
  • 为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听Uncaught Promise Error

    window.addEventListener("unhandledrejection", function(e){
      console.log(e);
    });
  • 补充:如果去掉控制台的异常显示,需要加上:event.preventDefault();

vue异常捕获

  • VUE errorHandler

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

React异常捕获

  • componentDidCatch(React16)
  • 新概念Error boundary(React16):UI的某部分引起的 JS 错误不会破坏整个程序(只有 class component可以成为一个 error boundaries)

    不会捕获以下错误

    1.事件处理器
    2.异步代码
    3.服务端的渲染代码
    4.在 error boundaries 区域内的错误
  • Eg: 全局一个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>

页面崩溃和卡顿处理

  • 卡顿

    • 网页暂时响应比较慢, JS 可能无法及时执行
    • 解决:window 对象的 loadbeforeunload 事件实现了网页崩溃的监控

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

    • JS 都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢
    • 解决:Service Worker 来实现网页崩溃的监控

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

错误上报机制

  • Ajax 发送数据
    因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。
  • 动态创建 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)      // 上报错误信息
  }
}

js源代码压缩如何定位:成熟方案提供sentry

sentry 是一个实时的错误日志追踪和聚合平台,包含了上面 sourcemap 方案,并支持更多功能,如:错误调用栈,log 信息,issue管理,多项目,多用户,提供多种语言客户端等,

这里不过多叙述,之后在搭建sentry服务时,会再补篇博文

补充:node服务端错误处理机制

全栈开发,后端采用express库,在这里补充一下,node服务的错误处理方案

  • 错误分类

    • 一般错误处理:如某种回退,基本上只是说:“有错误,请再试一次或联系我们”。这并不是特别聪明,但至少通知用户,有地方错了——而不是无限加载或进行类似地处理
    • 特殊错误处理为用户提供详细信息,让用户了解有什么问题以及如何解决它,例如,有信息丢失,数据库中的条目已经存在等等
  • 步骤

    • 1. 构建一个自定义 Error 构造函数:让我们方便地获得堆栈跟踪

      class CustomError extends Error {
          constructor(code = 'GENERIC', status = 500, ...params) {
              super(...params)
              if (Error.captureStackTrace) {
                  Error.captureStackTrace(this, CustomError)
              }
              this.code = code
              this.status = status
          }
      }
      
      module.exports = CustomError
    • 2.处理路由:对于每一个路由,我们要有相同的错误处理行为

      wT:在默认情况下,由于路由都是封装的,所以 Express 并不真正支持那种方式

      解决:实现一个路由处理程序,并把实际的路由逻辑定义为普通的函数。这样,如果路由功能(或任何内部函数)抛出一个错误,它将返回到路由处理程序,然后可以传给前端

      const express = require('express')
      const router = express.Router()
      const CustomError = require('../CustomError')
      
      router.use(async (req, res) => {
          try {
              const route = require(`.${req.path}`)[req.method]
      
              try {
                  const result = route(req) // We pass the request to the route function
                  res.send(result) // We just send to the client what we get returned from the route function
              } catch (err) {
                  /*
                  This will be entered, if an error occurs inside the route function.
                  */
                  if (err instanceof CustomError) {
                      /* 
                      In case the error has already been handled, we just transform the error 
                      to our return object.
                      */
      
                      return res.status(err.status).send({
                          error: err.code,
                          description: err.message,
                      })
                  } else {
                      console.error(err) // For debugging reasons
      
                      // It would be an unhandled error, here we can just return our generic error object.
                      return res.status(500).send({
                          error: 'GENERIC',
                          description: 'Something went wrong. Please try again or contact support.',
                      })
                  }
              }
          } catch (err) {
              /* 
              This will be entered, if the require fails, meaning there is either 
              no file with the name of the request path or no exported function 
              with the given request method.
              */
              res.status(404).send({
                  error: 'NOT_FOUND',
                  description: 'The resource you tried to access does not exist.',
              })
          }
      })
      
      module.exports = router
      
      // 实际路由文件
      const CustomError = require('../CustomError')
      
      const GET = req => {
          // example for success
          return { name: 'Rio de Janeiro' }
      }
      
      const POST = req => {
          // example for unhandled error
          throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
      }
      
      const DELETE = req => {
          // example for handled error
          throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
      }
      
      const PATCH = req => {
          // example for catching errors and using a CustomError
          try {
              // something bad happens here
              throw new Error('Some internal error')
          } catch (err) {
              console.error(err) // decide what you want to do here
      
              throw new CustomError(
                  'CITY_NOT_EDITABLE',
                  400,
                  'The city you are trying to edit is not editable.'
              )
          }
      }
      
      module.exports = {
          GET,
          POST,
          DELETE,
          PATCH,
      }
    • 3.构建全局错误处理机制

      process.on('uncaughtException', (error: any) => {
          logger.error('uncaughtException', error)
      })
      
      process.on('unhandledRejection', (error: any) => {
          logger.error('unhandledRejection', error)
      })

总结:

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

引用


Rainie
441 声望36 粉丝

全栈攻城狮