この章では、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) では、次の構造がアクティブです
processFile() extractAllEntries(...) fileNames.forEach(...) function (fileName) { ... } try { ... } catch (exception) { ... } extractOneEntry(...) openFile(...)
(1) の `throw` ステートメントは、そのツリーを上に辿り、アクティブな `try` ステートメントに遭遇するまで、すべての構造を離れます。そして、そのステートメントの `catch` ブロックを呼び出し、例外値を渡します。
JavaScriptの例外処理は、ほとんどのプログラミング言語と同様に機能します。`try` ステートメントはステートメントをグループ化し、それらのステートメントで例外をインターセプトできるようにします。
`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` の少なくとも1つも必要です。
try
{
«
try_statements
»
}
⟦
catch
(
«
exceptionVar
»
)
{
«
catch_statements
»
}
⟧
⟦
finally
{
«
finally_statements
»
}
⟧
仕組みは次のとおりです
`finally` は常に実行されます。`try_statements`(またはそれらが呼び出す関数)で何が起こっても関係ありません。`try_statements` で何が起こっても必ず実行されるべきクリーンアップ操作に使用します。
var
resource
=
allocateResource
();
try
{
...
}
finally
{
resource
.
deallocate
();
}
`try_statements` の1つが `return` である場合、`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仕様からの引用です。
`RangeError` は「数値が許容範囲を超えていることを示します。」たとえば:
> new Array(-1) RangeError: Invalid array length
`ReferenceError` は「無効な参照値が検出されたことを示します。」通常、これは未知の変数です。たとえば:
> unknownVariable ReferenceError: unknownVariable is not defined
`SyntaxError` は「通常のコードの解析中または `eval()` の引数の解析中に解析エラーが発生したことを示します。」たとえば:
> 3..1 SyntaxError: Unexpected number '.1'. Parse error. > eval('5 +') SyntaxError: Unexpected end of script
`TypeError` は「オペランドの実際の型が予期される型と異なることを示します。」たとえば:
> undefined.foo TypeError: Cannot read property 'foo' of undefined
`URIError` は「グローバルURI処理関数の1つが、その定義と互換性のない方法で使用されたことを示します。」たとえば:
> decodeURI('%2') URIError: URI malformed
エラーのプロパティは次のとおりです。
message
name
stack
エラーの通常の発生源は、外部(間違った入力、ファイルの欠落など)または内部(プログラムのバグ)のいずれかです。特に後者の場合、予期しない例外が発生し、デバッグする必要があります。多くの場合、デバッガーは実行されていません。「手動」デバッグには、次の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