圖片來源:Proper Error Handling in JavaScript @ Scotch |
keywords: exception handling, javascript, 錯誤處理, 例外處理
本文主要內容翻譯自 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 這個類型用來代表一般的錯誤情況,最常用在客製化例外情況。透過下面的指令可以產生一個錯誤物件的實例:
var error = new Error("error message"); console.log(error) // Error: error message
這個 Error 物件包含兩個屬性-name 和 message:
- 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 的通常是在編譯的過程中(compulation 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 區塊中可以指定一個參數,這個參數通常稱作 exception 或 catchID。在 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 回傳的結果,即時在 try 或 catch 中有 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
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 } }
參考
- Exceptional Exception Handling in JavaScript @ SitePoint
- A Guide to Proper Error Handling in JavaScript @ SitePoint
- Control Flow and Error Handling @ JavaScript Guide in MDN
0 意見:
張貼留言