プログラムにおいて処理を続行することが不可能なエラーが発生した場合には、例外処理を行います。
例外処理は、本来実行したい処理とは異なる部分ですが、全く記述しなかったり、不適切に記述すると、エラー発生時にシステムが停止してしまったり、必要な情報を残すことができなかったりするなどの重大な事態を招いてしまう恐れがあります。
ですから、実際の開発現場でプログラミングをするには、必須の処理となります。
本記事では、JavaScriptにおける例外処理についてまとめます。
プログラミング初心者の方の学習や、忘れてしまった方の復習として、参考にしていただければ幸いです。
記載しているプログラムは、Node.js12.18.4を使って動作確認をしています。
JavaScriptの例外処理
JavaScriptの例外処理に関連する記述は以下のとおりです。
- try…catch文:tryブロック内で発生した例外をcatchブロックで補足する。
- throw文:プログラマーが意図的に例外処理を発生させる。
- finally文:例外が発生してもしなくても際にも必ず実行される処理。
try…catch文
例外が発生する可能性がある文をtryブロックで括ります。
例外が発生すると、try文の例外発生以降の処理はスキップされ、catchブロックの中が実行されます。
function main() { try { console.log("before exception."); throwTestException(); console.log("after exception."); } catch (error) { console.error(error.message); } }
上記の関数mainでは、throwTestExceptionという関数を呼び出していますが、その関数内では必ず例外が発生するとします。
例外が発生すると、それ以降の処理はスキップして、catchブロックの処理が実行されます。
つまり、上記のプログラムでは、”before exception.”が表示された後に、”after exception.”は表示されず、catchブロックのconsole.error()の処理が実行されます。
throw文
throw文を使うことで、プログラマーは任意の場所で例外を発生させることができます。
function throwTestException() { console.log("before throw."); throw Error("test error."); console.log("after throw."); }
上記の関数throwTestExceptionでは、この関数が呼び出されると必ず例外が発生します。
例外が発生すると、それ以降の処理はスキップし、この関数を抜けて呼び出し元の処理に戻ります。
つまり、上記のプログラムでは、”before throw.”が表示された後に、”after throw.”は表示されず、この関数を抜けます。
finally文
finally文を使うと、例外が発生してもしなくても、どちらの場合でも必ず処理を実行させることができます。
function main() { try { console.log("before exception."); throwTestException(); console.log("after exception."); } catch (error) { console.error("catch exception."); } finally { console.log("finally."); } }
上記のプログラムでは、例外が発生しない場合は、”before exception.” → “after exception.” → “finally.”の順に表示され、tryブロックの後に、finallyブロックが処理されます。
例外が発生した場合は、”before exception.” → “catch ecxeption.” → “finally.”の順に表示され、catchブロックの後に、finallyブロックが処理されます。
finallyでは、基本的に例外が発生しても、しなくても必ず必要となる、リソース開放などの事後処理を記述することが多いです。
プログラムの構成によっては、catchブロックは記述せずに、try…finally文だけを記述する場合もあります。
エラーオブジェクト
catch文では、例外情報としてオブジェクトを補足することができます。
function main() { try { console.log("before exception."); throwTestException(); console.log("after exception."); } catch (error) { console.error(error.message); } }
上記の例では、errorという変数に補足したオブジェクトを格納し、コンソールにerrorが持つメッセージを表示しています。
catchでは任意の型のオブジェクトを補足できます。
逆に、自分でthrowする場合には、任意の型のオブジェクトを例外情報として指定することができます。
function throwTestException() { throw Error("test error."); }
上記の例では、JavaScriptが標準で提供している、Errorオブジェクトを使用しています。
エラーの種類を区別するために、JavaScriptでは、ビルトインエラーとして標準でいくつかのエラーオブジェクトを提供しています。
- RangeError:配列のインデックスを超えた参照をした場合のエラー
- ReferenceError:存在しない変数が参照された場合のエラー
- SyntaxError:構文エラーや文法誤りがあった場合のエラー
- TypeError:値が期待される型でない場合のエラー
これらを自分でthrowする時に、指定することもできます。
適切なエラーの種類がない場合は、独自のオブジェクトを作成することも可能です。
開発現場によっては、独自のエラーオブジェクトを用意していて、発生したケースに応じて使い分けたりします。
独自のエラーオブジェクトを作成する際には、基本的にErrorオブジェクトを継承して作成することが多いです。
その理由は、後述するログに関連するためです。
ログ出力
例外が発生した際に、後からなぜ発生したのかを調査する際に重要となるのがログです。
開発中はデバッグなどにより原因を特定することができますが、本番運用が始まるとデバッグなどはできないので、ログだけが頼りになります。
原因を特定するためには、原因が分かるようなログを出力する必要があります。
その1つがスタックトレースです。
スタックトレースは、プログラムを処理してきた順序(関数の呼び出し階層)が記録されていて、どこで例外が発生したのかを特定するために必要な情報となります。
スタックトレースは、Errorオブジェクトが持つ、stackプロパティに格納されています。
例外発生時のログ出力には、基本的にこのスタックトレースとともに、他にデータを特定できる情報(レコードのIDや名称など)や引数の情報(不正なデータが渡されていないか)などを出力することが多いかと思います。
どんな情報をログ出力すべきかは、経験によるところもあるのですが、自分が調査する立場ならどんな情報が必要となりそうかを考えてみると良いと思います。
例外処理は障害発生の最後の砦
プログラムは必ずと言ってよいほどエラーが発生します。
プログラム自体が完璧であったとしても、連携しているデータベースやネットワーク、OSなどあらゆるところで想定しないエラーが発生します。
プログラマーとしては、あらゆる事態を想定して、エラーが発生してもなるべく影響を少なくし、原因調査のために必要な情報を残すという点を心がける必要があります。
僕個人としては、正常系のプログラムであれば誰でも書けますが、この例外処理を無駄なく適切に書けるプログラマーが良いプログラマーであると思っています。
今回は、JavaScriptの例外処理についてまとめました。
参考になれば幸いです。
コメント