HOWTO · JavaScript
在 JavaScript 中使用不帶 Catch 的 try 語句
在今天的文章中,我們將學習在 JavaScript 中使用不帶 Catch 的 try 語句。
本頁內容
是的 —— JavaScript 的 try 語句可以省略 catch,只要提供 finally 子句即可。本教學說明何時可以不帶 catch 使用 try、被拋出的錯誤會如何傳播,以及這種寫法在什麼情況下才是合適的工具。
在 JavaScript 中 try 如何不帶 catch 運作
JavaScript 的 try 語句用來包住可能拋出錯誤的程式碼,只要是可以寫語句的位置都有效:腳本的頂層、函式內部,或巢狀於另一個 try 之中。它唯一做不到的是單獨存在 —— try 區塊後面必須接上 catch 子句、finally 子句,或兩者皆有,因此兩者都沒有的裸 try 是語法錯誤。大括號 {} 永遠是必要的,即使只有一條語句也一樣。
語法:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
try {
// try_statements: code that may throw
} catch (exception_var) {
// catch_statements: handle the thrown value
} finally {
// finally_statements: always run afterward
}
try_statements是要執行的語句。catch_statements是當try區塊中拋出值時要執行的語句。exception_var是選用的識別符,用來在對應的catch區塊中保存被拋出的值。finally_statements會在try語句完成後執行,無論是否拋出或捕獲了值。
由於 catch 只有在 finally 存在時才可省略(反之亦然),文法恰好允許三種組合:
try...catchtry...finallytry...catch...finally
當 catch 區塊存在,且 try 區塊內的語句(或它呼叫的函式)拋出值時,控制權會立即轉移到 catch;如果沒有拋出任何值,catch 會被完全略過。finally 區塊一律在 try 與 catch 區塊結束後執行,無論是否拋出值,也無論 catch 是否處理了它。
沒有 catch 子句時,finally 仍然會先執行,然後被拋出的值會繼續向呼叫者傳播。這點同樣適用於巢狀結構:try 語句可以互相巢狀,當內層 try 語句沒有 catch 時,就改由外層 try 的 catch 區塊來處理錯誤。
完整文法請參閱 MDN 的 try...catch 文件。
範例:不帶 catch 的 try...finally
下面的程式片段把一個本身沒有 catch 的 try...finally 放進外層的 try...catch 之中,這樣我們就能觀察 finally 區塊的執行,並看到被拋出的值最後去了哪裡。0 / 0 的運算結果是 NaN,所以 throw new Error(0 / 0) 會拋出一個訊息為 NaN 的 Error。
執行這個範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
try {
try {
console.log('Executing try block')
throw new Error(0 / 0)
} finally {
console.log('Final Call')
}
} catch (error) {
console.log('Outer catch received: ' + error.message)
}
Executing try block
Final Call
Outer catch received: NaN
內層的 try 會先印出它的日誌行,然後拋出錯誤。因為它沒有 catch,控制權會直接轉移到 finally,印出 Final Call(此為範例程式中保持英文的輸出字串)。只有當 finally 完成後,Error 才會繼續傳播,由外層的 catch 接收並印出其 NaN 訊息。如果沒有那個外層 catch,同樣的錯誤會傳到呼叫者;在頂層時,則會在印出 Final Call 之後以未捕獲錯誤的形式浮現。
何時使用不帶 catch 的 try
當某個區塊需要保證完成清理工作、但錯誤本身應交由呼叫者處理時,就適合使用 try...finally。finally 的典型工作包括釋放資源、關閉連線或檔案控制代碼、還原共享狀態(例如重新啟用按鈕或重設旗標),以及停止計時器 —— 這些工作無論 try 區塊是正常結束還是拋出錯誤都必須完成。
當程式對錯誤沒有任何有意義的處理可做時,這種寫法就是正確的選擇:原封不動地重新拋出只會增加雜訊,而空的 catch 區塊則會悄悄吞掉所有失敗,讓 bug 在遠離根源的地方才浮現。try...finally 保留了清理保證,同時仍讓呼叫者看見並處理錯誤。如果函式真的需要檢查被拋出的值、記錄它或從中復原,請改用真正的 catch 子句。
不帶 catch 的 try 的限制與陷阱
finally從不處理或壓制錯誤。finally執行完畢後,未處理的拋出值會繼續向呼叫者傳播;在頂層時,它會終止腳本或以未捕獲錯誤的形式回報。- 你無法觀察被拋出的值。沒有
catch,這個結構就無法取得被拋出的內容,因此不能記錄訊息或依錯誤型別分流。 finally內的控制流程會覆寫原本待定的流程。在finally中使用return或throw會丟棄仍在傳播中的值 —— 包括被拋出的錯誤,它會就此悄悄消失 —— 所以finally應只用於既不回傳也不拋出的簡單清理工作。try...finally之後的語句只有在沒有拋出任何值時才會執行,而finally本身也不能依賴try區塊成功執行,因為它在失敗路徑上同樣會執行。