JavaScript 中的錯誤處理機制

錯誤處理在開發和調試過程當中都顯得尤其重要。有些沒有進行錯誤處理的應用,直接就將瀏覽器的錯誤展現給了用戶,極大的下降了用戶體驗。好比有些很 low 的網站,打開某些頁面就直接彈出 "object" 這樣的錯誤,用戶看到以後一臉懵逼,心想這是什麼鬼?讓人感受極其的不專業。可見錯誤處理對一個應用來講是多麼的重要。javascript

這篇文章主要是給你們科普一些關於錯誤處理的知識,讓你們在腦海中有一個概覽。下一篇文章中我會結合具體的項目以及當前主流的一些框架,好比react, redux,來更深刻的介紹如何運用這些框架去封裝一整套錯誤處理的解決方案。java

error 對象

Error 構造對象能夠實例化一個 error 對象 (也就是Error 實例),而 error 對象就是一個包含了錯誤信息的對象。當代碼解析或者運行時發生錯誤,javascript 引擎就會自動產生並拋出一個 error 對象, 而後程序就中斷在發生錯誤的地方。react

示例:redux

const error = new Error('Whoop!');
error.message; // Whoop!
error.name; // Error
error.stack; // "Error: Whoops! at <anonymous>:1:13"

咱們經常使用的 messagename 都是 error 的標準屬性,因爲各個瀏覽器廠商對 error 進行了不一樣的擴展,因此在不一樣的瀏覽器中,error 也有不一樣的屬性和方法, 非標準屬性中咱們經常使用的是 stack 屬性(不少瀏覽器都擴展了這一屬性), 它用來表示棧跟蹤信息。數組

屬性 含義
message 錯誤信息
name 錯誤類型
constructor 指定一個函數用來建立實例的原型,也就是指定構造器(建立自定義 Error 會用到)
stack (非標準) 棧跟蹤信息

error 類型

除了普通的 Error 構造對象之外, javascript 還實現了其餘幾種主要的 error 構造對象1.promise

類型 解析 實例
EvalError eval錯誤。跟全局函數 eval() 有關的錯誤,在 ES5 以後已經再也不出現了
InternalError 內部錯誤。由 JavaScript 引擎拋出的錯誤
RangeError 範圍錯誤。發生在一個數值或參數超出其合法範圍,主要包括超出數組範圍或者超出數字取值範圍 new Array(-1); (1234).toExponential(21);
ReferenceError 引用錯誤。一般是因爲引用了一個不存在的值。 a.toString();
SyntaxError 語法錯誤。 a ? a+1;
TypeError 類型錯誤。一般是由於在執行特定的類型操做時,變量的類型不符合要求。例如變量中保存着意外類型,訪問不存在的方法等。 var a = new {}; var a = {a:1}; a.reverse(); // 對象並無 reverser 方法
URIError decodeURI() 或者 encodeURI() 傳入非法參數時,也包括 encodeURIComponent() 和 decodeURIComponent() decodeURI('http://www.test.com&%'); encodeURIComponent('uD800');

encodeURI 和 encodeURIComponent 的區別

這裏順便說一下 encodeURIencodeURIComponent 的區別。已經瞭解的同窗能夠忽略這一小部分,繼續往前面看。瀏覽器

encodeURI 是對統一資源標識符 (URI) 所有編碼,而 encodeURIComponent 對統一資源標識符 (URI) 部分編碼

假設一個 URI 是一個完整的 URI, 那麼咱們沒必要對那些在 URI 中保留的而且帶有特殊含義的字符進行編碼。因爲 encodeURI 會替換掉全部字符,可是卻不包含一些保留字符,如 "&", "+", "=" 等(這些字符在 GET 和 POST 請求中是特殊字符,須要被編碼),因此 encodeURI 自己沒法產生能使用與 HTTP GET 或者 POST 請求的 URI。可是咱們可使用 encodeURIComponent 來對這些字符進行編碼。服務器

encodeURIComponent 轉義除了字母、數字、(、)、.、!、~、*、'、-和_以外的全部字符。
爲了不服務器收到不可預知的請求,對任何用戶輸入的做爲 URI 部分的內容都須要用 encodeURIComponent 進行轉義。

拋出和捕獲錯誤 throw and try...catch

一般使用 throw 語句拋出錯誤,並用 try...catch 進行捕獲。一般會把全部可能會拋出錯誤的代碼都放在 try 語句塊中,而把那些用於錯誤處理的代碼放在 catch 塊中。微信

throw 語句

throw 過程是阻塞的,程序會中斷在第一個拋出錯誤的地方,因此後面的代碼不會執行。session

throw new SyntaxError('this is syntax error'); 
throw 123; // 不執行
throw 'hi there'; // 不執行
throw true;  // 不執行

catch 語句

catch 代碼塊捕獲錯誤以後,程序不會中斷,會按照正常流程繼續執行下去。

try {
  throw new Error('Whoops!');
} catch (e) {
  console.log(e.name + ':' + e.message);
}
console.log('hello!');

// Error:Whoops!
// hello!

finally 語句

finallytry...catch 中是可選的,可是一旦使用了它,它裏面的代碼就必定會被執行,也就是說無論 try 語句塊中的代碼是否正常執行,finnaly 都會被執行。正以下面的代碼, 即便在 try 中資源被阻塞,因爲咱們在 finnaly 中執行了關閉操做,文件最後仍是會被關閉。

openMyFile()
try {
   // 阻塞資源
   writeMyFile(theData);
} finally {
   closeMyFile(); // 始終會關閉資源
}

處理一個特定的錯誤。

try {
      foo.bar();
    } catch (e) {
      switch (e.name) {
        case 'RangeError':
          //do something
          console.log('RangeError: ' + e.message);
          break;
        case 'ReferenceError':
          //do something
          console.log('ReferenceError: ' + e.message);
          break;
        default:
          console.log(e.name + ':' + e.message);
      }
    }

error 事件

任何沒有 catch 的錯誤都會觸發 window 對象的 error 事件。

error 事件能夠接收三個參數:錯誤消息、錯誤所在的 URL 和行號。你能夠經過如下兩種方式給 window 綁上 error 事件2

// message: 錯誤消息, source: 發生錯誤文件的 URL, lineno: 錯誤行號

  // 方法一
  window.onerror = function(messageOrEvent, source, lineno, colno, error) {
    alert(messageOrEvent, source, lineno, colno, error);
  }
  
  or 
  
  window.onerror = console.log;
  throw new Error('whoops!');
  
  // 方法二 
  window.addEventListener('error', function(errorEvent){
      alert(errorEvent.error);
  });

在實際狀況中,error 事件並不經常使用(可是在某些狀況下,如微信端的調試,error 事件仍是挺有用的),由於咱們仍是但願全部的異常都能獲得很好的處理,而不是把錯誤交給瀏覽器。但有的時候並非全部的錯誤都可以被撲獲,而且某些業務場景會使用到追蹤瀏覽器報錯的工具,這時候可能就須要將瀏覽器的錯誤拋出去,因此在這種狀況下也須要去全局監聽 error 事件。

自定義錯誤類型 Custom Error Types

建立一個自定義類 CustomError, 以方便去擴展更多的自定義 Error

CustomError.js

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    if (typeof Error.captureStackTrace === 'function') {
      // 在瀏覽器領域,除了使用V8引擎的 Chrome,
      // 其它瀏覽器中不存在 Error.captureStackTrace()這一接口,
      // 因此在這裏作一個條件判斷。
      Error.captureStackTrace(this, this.constructor); // 返回調用堆棧信息, 用於在 error 對象上添加合理的 stack 屬性。
    } else {
      this.stack = new Error(message).stack;
    }
  }
}

Error.captureStackTrace

Error.captureStackTrace 是用來在 targetObject 中添加一個 .stack 屬性。對該屬性進行訪問時,將以字符串的形式返回 Error.captureStackTrace() 語句被調用時的代碼位置信息(即:調用棧歷史)。

Error.captureStackTrace(targetObject[, constructorOpt])

除了 targetObject, captureStackTrace 還接受一個類型爲 function 的可選參數 constructorOpt,當傳遞該參數時,調用棧中全部 constructorOpt 函數之上的信息(包括 constructorOpt 函數自身),都會在訪問 targetObject.stack 時被忽略。當須要對終端用戶隱藏內部的技術細節時, constructorOpt 參數會頗有用。

擴展自定義 Error 類型

經過基類 CustomError,咱們能夠建立出更多的自定義 Error, 好比下面的 HttpRequestErrorLoginExpiredError

HttpRequestError.js

class HttpRequestError extends CustomError {
  constructor(message, requestId, code, httpStatusCode) {
    const defaultAPIErrorMessage = () => {
      // 檢查是否有網
      return window.navigator.onLine ? 'Something wrong' : 'No connection';
    };

    message = message || defaultAPIErrorMessage();
    super(message);

    this.requestId = requestId;
    this.code = code;
    this.httpStatusCode = httpStatusCode;
  }
}

throw new HttpRequestError(null, 'requestId', 'code', 'httpStatusCode');

LoginExpiredError.js

class LoginExpiredError extends CustomError {
  constructor(message) {
    message = message || 'Your session has expired!';

    super(message);
  }
}

throw new LoginExpiredError();

當咱們建立了各類自定義 Error 以後,咱們能夠在不一樣的場景去使用它們了,好比在 http 請求失敗的時候拋出 HttpRequestError,並彈出對話框提示用戶。在用戶登陸過時以後,拋出 LoginExpiredError,彈出對話框,並自動 logout 等等。經過拋出不一樣的 Error 類型,才能讓咱們進行不一樣的撲獲處理。

const deffer = Q.deffer();

  if (expired) {
    deffer.reject(new LoginExpiredError()); // 經過 promise 拋出異常
  }

  if (error instanceof LoginExpiredError) { // 撲獲異常
    logout();
  }

結尾

關於 Error 的介紹就先講到這裏。


  1. Error in MDN
  2. window.onerror
相關文章
相關標籤/搜索