第 14 章。例外處理
目錄
購買書籍
(廣告,請勿封鎖。)

第 14 章。例外處理

本章說明 JavaScript 的例外處理運作方式。它從對例外處理的一般說明開始。

什麼是例外處理?

在例外處理中,您經常將緊密結合的陳述分組。如果您在執行這些陳述時,其中一個導致錯誤,那麼繼續執行其餘陳述沒有任何意義。相反,您會盡可能優雅地嘗試從錯誤中復原。這讓人聯想到交易(但沒有原子性)。

讓我們看看沒有例外處理的程式碼

function processFiles() {
    var fileNames = collectFileNames();
    var entries = extractAllEntries(fileNames);
    processEntries(entries);
}
function extractAllEntries(fileNames) {
    var allEntries = new Entries();
    fileNames.forEach(function (fileName) {
        var entry = extractOneEntry(fileName);
        allEntries.add(entry);  // (1)
    });
}
function extractOneEntry(fileName) {
    var file = openFile(fileName);  // (2)
    ...
}
...

對 (2) 中 openFile() 的錯誤做出反應的最佳方式是什麼?顯然,陳述 (1) 不應該再執行。但我們也不想中止 extractAllEntries()。相反,跳過當前檔案並繼續執行下一個檔案就足夠了。為此,我們將例外處理新增到先前的程式碼

function extractAllEntries(fileNames) {
    var allEntries = new Entries();
    fileNames.forEach(function (fileName) {
        try {
            var entry = extractOneEntry(fileName);
            allEntries.add(entry);
        } catch (exception) {  // (2)
            errorLog.log('Error in '+fileName, exception);
        }
    });
}
function extractOneEntry(fileName) {
    var file = openFile(fileName);
    ...
}
function openFile(fileName) {
    if (!exists(fileName)) {
        throw new Error('Could not find file '+fileName); // (1)
    }
    ...
}

例外處理有兩個面向

  1. 如果有一個無法在發生處有意義地處理的問題,請擲回例外。
  2. 找到可以處理錯誤的位置:捕捉例外。

在 (1) 中,下列建構活躍

    processFile()
        extractAllEntries(...)
            fileNames.forEach(...)
                function (fileName) { ... }
                    try { ... } catch (exception) { ... }
                        extractOneEntry(...)
                            openFile(...)

在 (1) 中的 throw 陳述會走上該樹狀結構,並離開所有建構,直到它遇到一個活躍的 try 陳述。然後它會呼叫該陳述的 catch 區塊,並將例外值傳遞給它。

JavaScript 中的例外處理

JavaScript 中的例外處理與大多數程式語言中的運作方式類似:一個 try 陳述會將陳述分組,並讓您攔截這些陳述中的例外。

throw

throw 的語法如下

throw «value»;

任何 JavaScript 值都可以被擲回。為了簡單起見,許多 JavaScript 程式只擲回字串

// Don't do this
if (somethingBadHappened) {
    throw 'Something bad happened';
}

不要這樣做。JavaScript 有用於例外物件的特殊建構函式(請參閱錯誤建構函式)。使用它們或對它們進行子類化(請參閱第 28 章)。它們的優點是,JavaScript 會自動新增堆疊追蹤(在大多數引擎上),而且它們有空間容納其他特定於內容的屬性。最簡單的解決方案是使用內建建構函式 Error()

if (somethingBadHappened) {
    throw new Error('Something bad happened');
}

try-catch-finally

try-catch-finally 的語法如下try 是強制性的,而且 catchfinally 中至少必須有一個:

try {
    «try_statements»
}
catch («exceptionVar») {
   «catch_statements»
}
finally {
   «finally_statements»
}

以下是它的運作方式

  • catch 會捕捉任何在 try_statements 中拋出的 例外狀況,無論是直接拋出或是在它們呼叫的函式中拋出。提示:如果您想要區分不同類型的例外狀況,您可以使用 constructor 屬性來切換例外狀況的建構函式(請參閱 建構函式屬性的使用案例)。
  • finally 總是會執行,無論在 try_statements(或是在它們呼叫的函式)中發生什麼事。將它用於清理作業,無論在 try_statements 中發生什麼事,都應該執行這些作業:

    var resource = allocateResource();
    try {
        ...
    } finally {
        resource.deallocate();
    }

    如果其中一個 try_statementsreturn,那麼 finally 區塊會在之後執行(在離開函式或方法之前;請參閱以下範例)。

範例

任何值都可以拋出

function throwIt(exception) {
    try {
        throw exception;
    } catch (e) {
        console.log('Caught: '+e);
    }
}

以下是互動方式

> throwIt(3);
Caught: 3
> throwIt('hello');
Caught: hello
> throwIt(new Error('An error happened'));
Caught: Error: An error happened

finally 總是會執行

function throwsError() {
    throw new Error('Sorry...');
}
function cleansUp() {
    try {
        throwsError();
    } finally {
        console.log('Performing clean-up');
    }
}

以下是互動方式

> cleansUp();
Performing clean-up
Error: Sorry...

finally 會在 一個 return 陳述式之後執行:

function idLog(x) {
    try {
        console.log(x);
        return 'result';
    } finally {
        console.log("FINALLY");
    }
}

以下是互動方式

> idLog('arg')
arg
FINALLY
'result'

回傳值會在執行 finally 之前排隊

var count = 0;
function countUp() {
    try {
        return count;
    } finally {
        count++;  // (1)
    }
}

在執行陳述式 (1) 時,count 的值已經排隊準備回傳

> countUp()
0
> count
1

錯誤建構函式

ECMAScript 標準化下列錯誤建構函式。說明摘錄自 ECMAScript 5 規範:

以下是錯誤的屬性:

訊息
錯誤訊息。
名稱
錯誤的名稱。
堆疊
堆疊追蹤。這是非標準的,但可在許多平台上使用,例如 Chrome、Node.js 和 Firefox。

堆疊追蹤

通常錯誤的來源可能是外部的(輸入錯誤、檔案遺失等)或內部的(程式中的錯誤)。特別是在後一種情況下,您會遇到意外的例外,需要進行偵錯。通常您沒有執行偵錯器。對於「手動」偵錯,以下兩項資訊很有用:

  1. 資料:變數有哪些值?
  2. 執行:例外發生在哪一行,以及哪些函式呼叫處於活動狀態?

您可以將第一個項目(資料)的一部分放入例外物件的訊息或屬性中。 第二個項目(執行)在許多 JavaScript 引擎中透過堆疊追蹤獲得支援,這是例外物件建立時呼叫堆疊的快照。以下範例會列印堆疊追蹤:

function catchIt() {
    try {
        throwIt();
    } catch (e) {
        console.log(e.stack); // print stack trace
    }
}
function throwIt() {
    throw new Error('');
}

以下是互動

> catchIt()
Error
    at throwIt (~/examples/throwcatch.js:9:11)
    at catchIt (~/examples/throwcatch.js:3:9)
    at repl:1:5
下一章:15. 函式