第14章 例外処理
目次
本書を購入する
(広告です。ブロックしないでください。)

第14章 例外処理

この章では、JavaScriptの例外処理の仕組みについて説明します。まずは、例外処理とは何かについての一般的な説明から始めます。

例外処理とは何か?

例外処理では、密接に関連するステートメントをグループ化することがよくあります。 これらのステートメントを実行している際に、いずれかのステートメントでエラーが発生した場合、残りのステートメントを続行しても意味がありません。代わりに、できるだけ gracefully にエラーから回復しようとします。これは、トランザクションを漠然と連想させます(ただし、アトミック性はありません)。

例外処理のないコードを見てみましょう

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)
    }
    ...
}

例外処理には2つの側面があります

  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` は必須であり、`catch` と `finally` の少なくとも1つも必要です。

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

仕組みは次のとおりです

任意の値をスローできます。

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仕様からの引用です。

エラーのプロパティは次のとおりです。

message
エラーメッセージ。
name
エラーの名前。
stack
スタックトレース。これは非標準ですが、多くのプラットフォーム(Chrome、Node.js、Firefoxなど)で利用できます。

スタックトレース

エラーの通常の発生源は、外部(間違った入力、ファイルの欠落など)または内部(プログラムのバグ)のいずれかです。特に後者の場合、予期しない例外が発生し、デバッグする必要があります。多くの場合、デバッガーは実行されていません。「手動」デバッグには、次の2つの情報が役立ちます。

  1. データ:変数にはどのような値がありますか?
  2. 実行:例外はどの行で発生し、どの関数呼び出しがアクティブでしたか?

最初の項目(データ)の一部を、メッセージまたは例外オブジェクトのプロパティに入れることができます。 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. 関数