青岩梦罢 一世万花

  • {{docContent.title}}

    前端容灾及异常处理

    说明:

    异常指的是在程序运行过程中发生的异常事件,有些错误是由于外部环境导致的,有些错误是由于开发人员疏忽所导致的,有效的处理这些错误,保证计算机世界的正常运转是我们开发人员必不可少的一环。

    为用户创造良好的用户体验,是前端开发者职责之所在。当页面发生错误的时候,相比于页面崩溃或点不动,在适当的时机,以一种适当的方式去提醒用户当前发生了什么,无疑是一种更友好的处理方式。

    项目中面临下面几种异常场景,需要处理:

    • 语法错误
    • 事件异常
    • HTTP请求异常
    • 静态资源加载异常
    • Promise 异常
    • Iframe 异常
    • 页面崩溃

    整体异常处理方案需要实现两个方面的效果:

    1. 提升用户体验
    2. 上报监控系统,能及时早发现、定位、解决问题

    错误类型

    ECMA-262规范定义的七种错误类型

    • Error
    • EvalError
    • RangeError
    • ReferenceError
    • SyntaxError
    • TypeError
    • URIError

    Error

    Error是所有错误的基类,其他错误都继承自该类型

    EvalError

    EvalError对象表示全局函数eval()中发生的错误。如果eval()中没有错误,则不会抛出该错误。可以通过构造函数创建这个对象的实例

    RangeError

    RangeError对象表示当一个值不在允许值的集合或范围内时出现错误。

    SyntaxError

    当JavaScript引擎在解析代码时遇到不符合该语言语法的标记或标记顺序时,将引发该异常:

    ReferenceError

    当引用不存在的变量时,该对象表示错误:

    TypeError

    传递给函数的操作数或实参与该操作符或函数期望的类型不兼容:

    !

    URIError

    当全局URI处理函数以错误的方式使用时:

    处理和防范

    上面我们提到错误和异常无处不在,存在于各式各样的应用场景中,那我们应该如何有效的拦截异常,将错误扼杀于摇篮之中,让用户无感呢?亦或者遇到致命错误时,进行降级处理?

    (1) try catch

    动机

    • 对可能发生错误的代码进行异常捕获;
    • 保证后面的代码继续运行。

    范围

    • 只能捕获同步代码所产生的运行时错误,对于语法错误和异步代码所产生的错误是无能为力的。

    (2) Promise.catch()

    动机

    • 用来捕获promise代码中的错误

    范围

    • 当遇到代码错误时,可以捕获:
    • 当遇到语法错误时,不能捕获。
    • 当遇到异步运行时错误时,不能捕获:

    (3) unhandledrejection

    1.用法

    unhandledrejection:当Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件

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

    2.动机

    为了防止有漏掉的 Promise 异常,可以在全局增加一个对 unhandledrejection 的监听进行兜底,用来全局监听Uncaught Promise Error。

    3.范围

     window.addEventListener("unhandledrejection", function (e) {
          console.log("捕获到的promise异常:", e);
          e.preventDefault();
        });
        new Promise((res) => {
            console.log(a);
        });
        // 捕获到的promise异常的: PromiseRejectionEvent
    

    注意:此段代码直接写在控制台是捕获不到promise异常的,写在html文件中可正常捕获。

    (4) window.onerror

    1.用法

    当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。

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

    2.动机

    众所周知,很多做错误监控和上报的类库就是基于这个特性来实现的,我们期待它能处理那些try...catch不能处理的错误。

    3.范围

    根据MDN的说法,wondow.onerror能捕获JavaScript运行时错误(包括语法错误)或一些资源错误。而在真正的测试过程中,wondow.onerror并不能捕获语法错误。

    window.onerror并不能捕获语法错误和静态资源的加载错误。同样也不能捕获异步代码的错误,但是window.onerror能捕获同样是异步代码的setTimeout和setInterval里面的错误。

    (5) window.addEventListener

    1.用法

    window.addEventListener('error',(error)=>{console.log(error)})
    

    2.动机

    • 希望用他来兜住window.onerror和try catch的底
    • 捕获到异步错误和资源的加载错误。

    3.范围

    <body>
         <img id="img" src="./fake.png" />
         <iframe id="iframe" src="./test4.html"></iframe>
      </body>
      <script>
        window.addEventListener(
          "error",
          function (error) {
              console.log(error, "error");
          },
          true
        );
        setTimeout(() => {
            console.log(a);
        });
        new Promise((resolve, reject) => {
            console.log(a);
        });
          console.log(b)
          var f=e, //语法异常
      </script>
    

    在此过程中,资源文件都是不存在的,我们发现window.addEventListener('error')依旧不能捕获语法错误,Promise异常和iframe异常。

    对于语法错误我们可以在编译过程中捕获,,Promise异常已在上面给出解决方案,现在还剩下iframe异常需要单独处理了。

    (5) iframe异常

    1.用法

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

    2.动机

    用来专门捕获iframe加载过程中的异常。

    3.范围

    在实际的测试过程中,该方法未能捕获到异常。

    (6) React中捕获异常

    部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。

    错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

    注意:错误边界无法捕获以下场景中产生的错误

    • 事件处理
    • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
    • 服务端渲染
    • 它自身抛出来的错误(并非它的子组件)

    如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。

    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error) {
        // 更新 state 使下一次渲染能够显示降级后的 UI
        return { hasError: true };
      }
    
      componentDidCatch(error, errorInfo) {
        // 你同样可以将错误日志上报给服务器
        logErrorToMyService(error, errorInfo);
      }
    
      render() {
        if (this.state.hasError) {
          // 你可以自定义降级后的 UI 并渲染
          return <h1>Something went wrong.</h1>;
        }
    
        return this.props.children; 
      }
    }
    

    错误边界的工作方式类似于 JavaScript 的 catch {},不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件。大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。(引用自React 官网)

    (7) Vue中捕获异常

    Vue.config.errorHandler = function (err, vm, info) {
      // handle error
      // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
      // 只在 2.2.0+ 可用
    }
    

    指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例。(引用自Vue 官网)

    • 从 2.2.0 起,这个钩子也会捕获组件生命周期钩子里的错误。同样的,当这个钩子是 undefined 时,被捕获的错误会通过 console.error 输出而避免应用崩溃。
    • 从 2.4.0 起,这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了。
    • 从 2.6.0 起,这个钩子也会捕获 v-on DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理。

    (8) http请求异常

    1.用法

    以axios为例,添加响应拦截器

    axios.interceptors.response.use(function (response) {
        // 对响应数据做点什么
        // response 是请求回来的数据
        return response;
      }, function (error) {
        // 对响应错误做点什么
        return Promise.reject(error)
      }
    )
    

    2.动机

    用来专门捕获HTTP请求异常

    前端容灾

    前端容灾指的因为各种原因后端接口挂了(比如服务器断电断网等等),前端依然能保证页面信息能完整展示。

    ** LocalStorage**

    首先,使用 LocalStorage

    在接口正常返回的时候把数据都存到 LocalStorage ,可以把接口路径作为 key,返回的数据作为 value。

    然后之次再请求,只要请求失败,就读取 LocalStorage,把上次的数据拿出来展示,并上报错误信息,以获得缓冲时间。

    CDN

    同时,每次更新都要备份一份静态数据放到CDN

    在接口请求失败的时候,并且 LocalStorage 也没有数据的情况下,就去 CDN 摘取备份的静态数据。

    ** Service Worker**

    假如不只是接口数据,整个 html 都想存起来,就可以使用 Service Worker 做离线存储

    利用 Service Worker 的请求拦截,不管是存接口数据,还是存页面静态资源文件都可以

    // 拦截所有请求事件 缓存中有请求的数据就直接用缓存,否则去请求数据 
    self.addEventListener('fetch', e => { 
        // 查找request中被缓存命中的response 
        e.respondWith(caches.match(e.request).then( response => { 
            if (response) { 
                return response 
            } 
            console.log('fetch source') 
        })) 
    })
    

    二次元技术宅

    © 2021 不求独避风雨外,只笑桃源非梦中

    Hide
    Switch
    Save
    切换主题 | SCHEME TOOL