詳解JS錯誤處理:前端JS/Vue/React/Iframe/跨域/Node

js錯誤捕獲

js錯誤的實質,也是發出一個事件,處理他javascript

error實例對象

  • 對象屬性html

    • message:錯誤提示信息
    • name:錯誤名稱(非標準屬性)宿主環境賦予
    • stack:錯誤的堆棧(非標準屬性)宿主環境賦予
  • 對象類型(7種)前端

    • SyntaxError對象是解析代碼時發生的語法錯誤
    • ReferenceError對象是引用一個不存在的變量時發生的錯誤
    • RangeError對象是一個值超出有效範圍時發生的錯誤(一是數組長度爲負數,二是Number對象的方法參數超出範圍,以及函數堆棧超過最大值)
    • TypeError對象是變量或參數不是預期類型時發生的錯誤:對字符串、布爾值、數值等原始類型的值使用new命令
    • URIError對象是 URI 相關函數的參數不正確時拋出的錯誤:使用函數不當
    • eval函數沒有被正確執行時,會拋出EvalError錯誤 - 再也不使用,爲了代碼兼容
  • 自定義錯誤vue

    function UserError(message) {
      this.message = message || '默認信息';
      this.name = 'UserError';
    }
    
    UserError.prototype = new Error();
    UserError.prototype.constructor = UserError;
    new UserError('這是自定義的錯誤!');

Js運行時錯誤處理機制

  • try..catch…finallyjava

    • 範圍:用來捕獲任何類型的同步錯誤,能夠捕獲async / await的代碼,但沒法捕獲promise、setTimeout、dom回調(eg:onclick點擊回調)的代碼,在回調函數裏面寫try…catch能夠捕獲,但包在外面不會捕獲,沒法捕獲語法錯誤
    • 異步不捕獲緣由:
    • async/await捕獲緣由:
  • window.onerrornode

    • 範圍:同步錯誤和異步錯誤均可以捕獲,但沒法捕獲到靜態資源異常,或者接口異常(網絡請求異常不會事件冒泡,所以必須在捕獲階段將其捕捉到才行),沒法捕獲語法錯誤
    • 原理:當 JS 運行時錯誤發生時,window 會觸發一個 ErrorEvent 接口的 error 事件
    • 參數git

      /**
      * @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() 處理函數,有瀏覽器兼容問題)
  • 注意:只能捕獲沒法冒泡github

    window.addEventListener('error', (error) => {
        console.log('捕獲到異常:', error);
    }, true) // 必定要加true,捕獲但不冒泡
  • Script error跨域的靜態資源加載異常捕獲(cdn文件等)數據庫

    跨域文件只會報Script error,沒有詳細信息,怎麼解決:express

    • 客戶端: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 解決

引用

相關文章
相關標籤/搜索