Skip to main content

[JS] Error Handling

keywords: exception handling, javascript, 錯誤處理, 例外處理#

重點#

  • try...catch... 的使用應該是用來處理「例外情況」,也就是沒有預期到會發生的事,不要把它當作 conditional flow 使用。
// Error Object
function throwError() {
try {
throw new Error('foobar');
} catch (ex) {
console.log(ex); // Error: foobar
console.log(ex.name); // Error;
console.log(ex.message); // foobar;
}
}
throwError();

本文主要內容翻譯自 Exceptional Exception Handling in JavaScript @ SitePoint

 在撰寫程式的過程中發生錯誤(error)或出現例外情況(exception)是經常出現的情況,一般我們會把「錯誤」稱作「例外」,兩者可以交替著使用。

當 JavaScript 程式執行的過程中發生錯誤時,它會丟出例外狀況(throw an exception),JS 並不會繼續往下走,而是尋找有沒有任何程式碼能夠處理這些錯誤(exception handling code),如果沒有找到任何可以處理錯誤狀況的程式碼,則它會從丟出例外狀況的函式中跳出(return),就這樣重複尋找錯誤處理的程式碼、跳出,直到觸及到最外層的函式(top level function)後終止。

錯誤類型#

當例外產生時,會產生一個用來代表錯誤的物件。在 JS 中內建了 7 種錯誤類型的物件:

1. Error#

Error @ MDN > Standard build-in objects

Error 這個類型用來代表一般的錯誤情況,最常用在客製化例外情況。透過下面的指令可以產生一個錯誤物件的實例:

var error = new Error('error message');
console.log(error); // Error: error message

這個 Error 物件包含兩個屬性-namemessage

  • name: 用來說明錯誤的類型(這裡就是 Error
  • message: 提供更多關於此例外情況的描述。

2. RangeError#

RangeError 的例外情況會發生在當數值落在特定的區間外時,例如,透過 toFixed() 方法時,它可以接受介於 0 ~ 20 的參數來說明要顯示到小數後第幾位,當這個參數超過這個區間時,就會拋出 RangeError

var pi = 3.14159;
pi.toFixed(100000); // RangeError: toFixed() digits argument must be between 0 and 100

3. ReferenceError(找不到變數:拼錯字)#

當試圖存取一個不存在的變數時,會拋出 ReferenceError,這個錯誤經常發生在拼錯字的情況。

console.log(bar); // ReferenceError: bar is not defined

4. SyntaxError(語法錯誤)#

當有程式碼違反 JavaScript 的語法規則時,會拋出 SyntaxError 的錯誤。熟悉 C 或 Java 的通常是在編譯的過程中(computation process)遇到語法錯誤;但 JavaScript 是直譯式語言(interpreted language),因此當程式碼被執行到時,才會辨認到語法錯誤。這種錯誤類型是所有例外狀況中唯一不能被修復的

if (false) { // SyntaxError: Unexpected token (3:0)
// 缺少結束的大括號

5. TypeError(找不到函式)#

如果某一變項的型別和所期待的操作不同時,會拋出 TypeError這經常發生在去呼叫執行一個不存在的函式時

/**
* 由於 foo 當中並不包含 bar 這個函式,因此會拋出 TypeError 錯誤
**/
var foo = {};
foo.bar(); // TypeError: foo.bar is not a function

6. URIError#

當使用 encodeURI()decodeURI() 的方法,但確有給了不合法的 URI 時會拋出這個錯誤:

/**
* "%" 表示的是 URI 中的跳脫片段,但在下面的例子中 "%" 後沒有接任和字串,因此是不合法的跳脫片段
**/
decodeURIComponent('%'); // URIError: URI malformed

7. EvalError#

eval() 這個函式不恰當的使用時,會拋出 EvalError 這個錯誤。這個錯誤不再被當前的 ECMAScript 規範所採用。

處理錯誤(Handling Exceptions)#

在 JavaScript 中如果碰到錯誤或例外情況時,如果沒有找到錯誤處理的程式,它會直接在那裡炸掉。那麼要如何避免錯誤產生時讓我們的程式直接炸掉呢?在 JavaScript 中,可以使用 try...catch...finally 語句。

try {
// attempt to execute this code
} catch (exception) {
// this code handles exceptions
} finally {
// this code always gets executed
}

try#

我們預期在 try 區塊內的程式碼會成功執行,但當有 try 區塊中有任何錯誤發生時,會立即進入 catch 的區塊;如果沒有錯誤發生,則會跳過 catch 區塊。finally 則是會在 try...catch... 之後先被執行。

catch#

catch 區塊中可以指定一個參數,這個參數通常稱作 exceptioncatchID。在 catch 區塊中可以辨認這個參數,但離開這個區塊後就無法取得這個變數。透過 catch 可以阻止例外情況繼續向外冒泡,讓整個程式可以繼續執行。

try {
foo++; // ReferenceError
} catch (exception) {
// ReferenceError: foo is not defined
console.log(`${exception.name}: ${exception.message}`);
}

finally#

finally 區塊中的程式碼會在 try, catch 後被執行,不論有沒有例外情況產生,因此 finally 區塊通常用來包含清除的程式碼(例如,closing files)。

但是如果 finally 區塊中有 return 值的話,這個值會是最後整個 try-catch-finally 回傳的結果,即時在 trycatch 中有 return 或其他的 throw 都會被忽略。

function f() {
try {
console.log(0);
throw 'bogus'; // 進入 catch
} catch (e) {
console.log(1);
return true; // 這個回傳值會被終止,直到整個 finally block 完成之後
console.log(2); // 執行不到
} finally {
console.log(3);
return false; // finally 中的 return 會覆蓋掉 try/catch 中的 return 或 throw
console.log(4); // 執行不到
}
// 執行 finally 中的 "return false"
console.log(5); // 執行不到
}
f(); // 0, 1, 3; returns false

客製化錯誤(Throwing Exceptions)#

JavaScript 允許開發者透過 throw 來拋出客製化的例外情形:

throw expression;

透過 throw 可以丟出幾乎任何型別的錯誤,但一般還是建議使用內建的例外類型(瀏覽器會提供比較多資訊)。

客製化錯誤訊息(message)#

例如,當我們透過除法卻不小心除了分母為 0 的數值時,可以使用 throw 來拋出例外情況:

let denominator = 0;
// RangeError: Attempted division by zero!
try {
if (denominator === 0) {
throw new RangeError('Attempted division by zero!');
}
} catch (e) {
console.log(e.name + ': ' + e.message);
}

客製化錯誤名稱(name)#

透過上面的方式我們可以根據原生的那 7 種錯誤類型(e.name)給予想要的錯誤訊息(e.message),但如果我們想要客製化錯誤名稱的話可以這麼做:

/**
* 客製化錯誤類型
**/
function DivisionByZeroError(message) {
this.name = 'DivisionByZeroError';
this.message = message;
}
// 繼承 Error 物件
DivisionByZeroError.prototype = new Error();
DivisionByZeroError.prototype.constructor = DivisionByZeroError;
// 建立 showError 的方法
DivisionByZeroError.prototype.showError = function () {
return this.name + ': "' + this.message + '"';
};

使用客製化錯誤類型:

let denominator = 0;
try {
if (denominator === 0) {
throw new DivisionByZeroError('Attempted division by zero!');
}
} catch (e) {
console.log(e.showError()); // DivisionByZeroError: "Attempted division by zero!"
}

程式範例 @ JSFiddle Why need to assign constructor back to my custom error type in JavaScript @ StackOverflow

其他範例#

透過 instanceof 處理多種不同的錯誤類型#

利用 instanceof 可以根據不同的錯誤類型執行不同的 catch

try {
// assume an exception occurs
} catch (exception) {
if (exception instanceof TypeError) {
// Handle TypeError exceptions
} else if (exception instanceof ReferenceError) {
// Handle ReferenceError exceptions
} else {
// Handle all other types of exceptions
}
}

參考#

待閱讀#

Last updated on