PJCHENder 未整理筆記

[JS] 錯誤處理 Error Handling

2017-12-04

[JS] 錯誤處理 Error Handling

@(JavaScript)

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

重點

1
2
3
4
5
6
7
8
9
10
11
12
// 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 這個類型用來代表一般的錯誤情況,最常用在客製化例外情況。透過下面的指令可以產生一個錯誤物件的實例:

1
2
var error = new Error("error message");
console.log(error) // Error: error message

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

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

2. RangeError

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

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

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

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

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

4. SyntaxError(語法錯誤)

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

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

5. TypeError(找不到函式)

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

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

6. URIError

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

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

7. EvalError

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

處理錯誤(Handling Exceptions)

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

1
2
3
4
5
6
7
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 可以阻止例外情況繼續向外冒泡,讓整個程式可以繼續執行。

1
2
3
4
5
6
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 都會被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 來拋出客製化的例外情形:

1
throw expression;

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

客製化錯誤訊息(message)

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

1
2
3
4
5
6
7
8
9
10
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),但如果我們想要客製化錯誤名稱的話可以這麼做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 客製化錯誤類型
**/
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 + '"';
}

使用客製化錯誤類型:

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
8
9
10
11
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
}
}

參考

待閱讀

掃描二維條碼,分享此文章