こんにちは!Webコーダー・プログラマーの貝原(@touhicomu)です。
今日はJavaScriptでよく使用されるtry~catch文について学習します。この記事では、try…catch文とは?やtry…catch文の使い方という基本的な内容から、
- 「try…catch文」の活用
- 「try…catch」のエラーオブジェクト
- 「try…catch文」の注意点
などの応用的な使い方に関しても学習していきます。このページで、try~catch文をよく把握して自分のスキルとしていきましょう!
「try…catch文」とは?
それでは、まず最初にtry…catch文の基本的な知識から学習を進めていきましょう!try…catch文は、予想していない異常によりエラーが発生するような場面で意図的に回避するための処理になります。例えば、次のようなプログラムがあるとします。
var result = 100 * num; console.log( result );
この例では、まったく変数宣言されていないnumという変数をいきなり1行目で使用していますね?そのためすぐにエラー(例外処理)となり、1行目でプログラムはストップします。これは単純な例ですが、実際の現場ではエラーになってストップして欲しくない場面は少なくありません。
例えば、関数の引数に想定してなかった値が渡されるようなケースでも、エラーを回避して別の処理を実行したいものです。そのような時にtry…catch文を利用すると、安全にエラーを回避して別の処理を実行することができるわけです。
try…catch文の使い方
try…catch文で例外を取得する方法
まずは、try…catch文の基本的な使い方から見ていきましょう!一般的な記述方法としては、次のようになります!
try { //例外エラーが発生するかもしれない処理 } catch( e ) { //例外エラーが起きた時に実行する処理 }
つまり、想定していなかったエラーが発生するかもしれない箇所にtry { }を挟み込むようなイメージです。そしてプログラムが異常終了しないような回避策をcatch() { }の中に記述することになります。冒頭で解説したサンプル例をtry…catch文で書き直すと次のようになります!
try { var result = 100 * num; console.log( result ); } catch(e) { console.log( e.message ); }
実行結果
num is not defined
この例では、try { }の中で宣言されていない変数numを使用しているのが分かります。そのため通常はエラーが発生して終了しますが、この場合はcatch() { }の処理が引き続き実行されます。
catch(e)には引数として例外処理の情報が格納されており、messageを付与するとエラー内容が確認できます。
throwで強制的に例外を発生させる方法
今度は、自らのコードで例外を発生させてみましょう。例外を発生させるにはthrow文を使用します。throwは(例外)を投げるという意味ですが、ここではErrorオブジェクトを投げて例外を発生させてみましょう。
Errorオブジェクトのコンストラクタには、エラーメッセージを代入することが可能です。
//try~catchで例外をthrowしてみよう。 window.onload = function () { try { // iを0から4まで繰り返し合計をjに入れる var i = 0; var j = 0; for (i = 0; i < 5; i++) { j += i; } // jが5より大きい場合、例外を発生させる if( j > 5 ) { throw new Error('jが5より大きいです。'); } // ダイアログにjを表示->ここまで処理は到達しない alert(j); } catch (e) { //例外エラーがおきたら、コンソールにログを出力する console.error("エラー:", e.message); } }
実行結果:
エラー: jが5より大きいです。
サンプルコードのように、jが5より大きいため、throw文のコードが実行され例外が発生しています。例外が発生した場合、処理のフローはcatch文に移り、catchの引数eにthrowされたオブジェクトが代入されます。
ここではErrorオブジェクトをthrowしていました。Errorオブジェクトのmessageプロパティにはエラーメッセージが格納されていますので、そのメッセージをコンソールに出力しています。
エラーオブジェクトの種類について
ここで、先ほど出てきたErrorオブジェクトについてもう少し解説をしておきます!Errorオブジェクトは一般的なエラー情報を出力する用途で使われるのですが、他にもいくつか種類があります。
オブジェクト名 | 用途 |
Error | 一般的なエラー全般で利用 |
ReferenceError | 宣言されていない変数によるエラー |
SyntaxError | プログラムの文法エラー |
TypeError | 想定された値と異なるデータ型によるエラー |
RangeError | 想定された許容範囲を超えた値によるエラー |
URIError | 不正なURIが指定されたことによるエラー |
EvalError | 不正なeval関数が実行されたエラー |
このように、エラーの種類が文法エラーなのか期待された値でないのか…など、その種類に応じていくつか用意されています。例えば、throwなどで意図的にエラーを発生させる場合には、最適なエラーオブジェクトを選択すると良いでしょう。
デバッグする時に、どのようなエラー処理なのかを素早く判断するのに最適です。
try~catch文の活用
複数のcatch文を実行する方法
catchが受け取る引数eはどんな値も代入できます。そのため、通常はcatch文を複数用意するオーバーロードはできません。
しかし、非推奨でFireFoxでしか動作しませんが、catchを複数用意できます。実際のサンプルコードを見ていきましょう。
// 複数のcatch文を書いてみよう // ※この構文は非推奨であり、FireFoxでしか動作しません。 window.onload = function () { try { var b = Boolean(true); var n = Number(1); var s = String('str'); var obj = Object('obj'); if (typeof (b) != "number") { throw "IllegalNumberException"; } if (typeof (n) != "boolean") { throw "IllegalBooleanException"; } if (typeof (obj) != "string") { throw "IllegalStringException"; } throw "Analysis Missed"; } catch (e if e == "IllegalNumberException") { console.error("エラー:", e); } catch (e if e == "IllegalBooleanException") { console.error("エラー:", e); } catch (e if e == "IllegalStringException") { console.error("エラー:", e); } catch (e) { console.error("エラー:", e); } }
実行結果:
エラー: IllegalNumberException
サンプルコードでは、catch文の中でif文を使っています。eがif文の条件にマッチする場合、マッチした箇所のcatch文が実行されます。サンプルコードでは色々な例外をthrowしていますが、どれもif文で識別選択されそれぞれに適したcatch文へ処理のフローが移ります。
そのため、例外の種類ごとにその例外に適したエラー処理を行うことが可能です。
「finally文」を使って必ず実行させる処理を記述する方法
今まで述べてきたようにtry…catch文は、通常処理部とエラー処理部を分けることができ、コードが簡潔になるというメリットがあります。
しかし、通常処理部で例外が発生した場合、処理のフローは例外処理部に移り、それ以降の通常処理部は実行されません。ここで、通常処理部で必ず実行されないといけないコードがあるとします。
例えば大きなメモリデータの解放処理やデータベースのコネクションの解除処理など、リソースの解放処理がそれにあたります。ここで、try…catch文に付属するfinally文が使用できます。try文で例外が発生しても、finally文配下のコードは必ず実行されます。
// finally文を書いてみよう window.onload = function () { try { var bigdata = "this is a big data ..............................."; throw new Error("error occurred"); } catch (e) { console.error("エラー:", e.message); } finally { console.log("finally"); bigdata = null; console.log('bigdata:', bigdata); } }
実行結果:
エラー: error occurred window.onload @ trycatchfnally.js finally bigdata: null
サンプルコードでは、仮にメモリを多く消費する変数bigdataがあるとします。これは、必要がなくなった場合、必ずメモリを解放しないといけません。そこで、bigdataのメモリの解放処理をfinally文内のコードで行っています。
実行結果を見てみると、bigdata=nullと表示されており、例外が発生してもfinally文配下のコードは必ず実行されます。そこで変数bigdataのメモリ解放を行いうなど、リソースの後処理などのコードを書いておくと便利です。
「try~catch」のエラーオブジェクト
エラー箇所の行番号(line number)を取得する方法
次に、例外が発生した箇所のfile nameとline numberによりファイル名と行番号をログに出力してみましょう。そのために、ErrorオブジェクトのfileNameプロパティとlineNumberプロパティを使用します。
なお、ErrorオブジェクトのfileNameプロパティとlineNumberプロパティは非推奨プロパティであり、FireFoxでしか使用できません。
// エラーオブジェクトでline numberを取得する // ※このコードはFireFoxでしか動作しません。 window.onload = function () { try { var j = 10; // jが5より大きい場合、例外を発生させる if (j > 5) { throw new Error('jが5より大きいです。'); } // ダイアログにjを表示->ここまで処理は到達しない alert(j); } catch (e) { var errMsg = ""; if (e.fileName && e.lineNumber) { // ファイル名と行番号が取得できたらメッセージとしてログに出力する errMsg = "ファイル名:" + e.fileName + "、 行番号:" + e.lineNumber; } else { errMsg = e.message; } //例外エラーがおきたら、コンソールにログを出力する console.error("エラー:", errMsg); } }
実行結果:
エラー: ファイル名:linenumber.js、 行番号:10
サンプルコードのように、fileNameプロパティとlineNumberプロパティにより、ファイル名と行番号を取得でき、ログに出力しています。
stackプロパティでトレースを行う方法
次に、Errorオブジェクトのstackプロパティを使て、エラー発生個所のスタックトレースをログに出力してみましょう。スタックトレースとは、文字通りエラー発生個所の詳細をトレース(追跡)する情報です。また、スタックとはエラー発生個所の関数の呼び出し履歴をスタック形式で表したものです。
実際にサンプルコードをみてみましょう。スタックを積んでいることを確認するために、例外を発生させる箇所は関数内にしています。
// エラーオブジェクトでstacktraceを取得する window.onload = function () { try { // 例外を発生させる関数 throwsException(); } catch (e) { //例外エラーがおきたら、スタックトレースをコンソールにログを出力する console.error(e.stack); } } function throwsException() { var j = 10; // jが5より大きい場合、例外を発生させる if (j > 5) { throw new Error('jが5より大きいです。'); } // ダイアログにjを表示->ここまで処理は到達しない alert(j); }
実行結果:
Error: jが5より大きいです。 at throwsException (stacktrace.js:20) at window.onload (stacktrace.js:6)
実行結果のように、例外の発生した関数(throwsException)とその行番号が出力されています。また、関数の呼び出し元の関数(onload)とその関数を呼び出した行番号も出力されています。これらの情報があると、どの箇所で例外が発生したのかすぐに分かります。
このように、スタックトレースはデバッグする際に貴重な情報となります。
エラー処理を関数化する方法
例外エラーが発生した時に実行するcatch文の処理は、関数化しておくとデバッグなどのケースで役立ちます。例えば、例外情報を出力するhandleError()という関数を作ってみましょう!
内容としては、これまで学習したエラーメッセージ、ファイル名・行番号スタックトレースを出力するものです。
function handleError(e) { var errMsg = "【エラーが発生しました】n"; errMsg += "メッセージ:" + e.message + "n"; if (e.fileName && e.lineNumber) { errMsg += "ファイル名:" + e.fileName + "、 行番号:" + e.lineNumber + "n"; } if (e.stack) { errMsg += "---- スタックトレース: ----n"; errMsg += e.stack + "n"; errMsg += "---------------------------n"; } console.error(errMsg); }
実行例
【エラーが発生しました】 メッセージ:jが5より大きいです。 ファイル名:errorfunc.js、 行番号:20 ---- スタックトレース: ---- throwsException@errorfunc.js:15 window.onload@errorfunc.js:9 ---------------------------
このように、handleError()メソッドを実行するだけでエラー情報の詳細がはっきりと出力されるわけです。あとは、この関数をtry…catch文に記述すればいつでも利用可能ですね。
try { //例外が発生する処理 } catch(e) { handleError(e) }
try…catch文の注意点
try…catch文ではcontinue、breakは使用できない
try…catch文では、元に戻って処理を継続するcontinue文、処理を途中で抜けるbreak文は使用できません。サンプルコードをみていきましょう。
// try~catchではcontinue、breakは使用できない window.onload = function () { try { // iを0から4まで繰り返し合計をjに入れる var i = 0; var j = 0; for (i = 0; i < 5; i++) { j += i; } // jが5より大きい場合、例外を発生させる if( j > 5 ) { throw new Error('jが5より大きいです。'); } // ダイアログにjを表示->ここまで処理は到達しない alert(j); } catch (e) { continue; //例外エラーがおきたら、コンソールにログを出力する console.error("エラー:", e.message); } }
実行結果:
Uncaught SyntaxError: Illegal continue statement: no surrounding iteration statement
continue文を使うと、シンタックスエラー(文法エラー)が出ていますね。次にbreak文のサンプルコードを見ていきましょう。
// try~catchではcontinue、breakは使用できない window.onload = function () { try { // iを0から4まで繰り返し合計をjに入れる var i = 0; var j = 0; for (i = 0; i < 5; i++) { j += i; } // jが5より大きい場合、例外を発生させる if( j > 5 ) { throw new Error('jが5より大きいです。'); } // ダイアログにjを表示->ここまで処理は到達しない alert(j); } catch (e) { break; //例外エラーがおきたら、コンソールにログを出力する console.error("エラー:", e.message); } }
実行結果:
Uncaught SyntaxError: Illegal break statement
break文もやはり、シンタックスエラー(文法エラー)が出ています。try…catch文内では、文法的にcontinue文とbreak文は使用できません。
try…catch文のパフォーマンスを下げない方法
最後に、try…catch文のパフォーマンスについて解説をしておきます。try…catch文は、プログラムを確実に実行するために必要な処理なのですが乱用は禁物です!と言うのも、処理の負荷が大きいのです。
特に、for文など繰り返し処理の中にtry…catch文を使ってしまうとパフォーマンスが落ちてしまうので要注意です。次のサンプル例を見てください!
var result = 0; console.time('count1'); for(var i=0; i<10000; i++) { try { result += i * num; //「num」は未宣言エラー } catch(e) { result += i; } } console.timeEnd('count1');
計測タイム
count1: 319.678ms
この例では、for文の中で未宣言の変数numを使った計算処理を行っており、エラーの場合は回避策を記述しています。処理に掛かった時間は319.678msでした。
しかし、これはあまり良いプログラムとは言えません。for文の中にtry…catch文を使っている点もそうですが、他に何か良い方法は考えられないでしょうか?
つまり、本当にtry…catch文を使うことがベストな選択なのかどうかを考えてみましょう!例えば次のように記述するとどうなるでしょう?
var result = 0; console.time('count2'); var num = num||1; //「num」が未宣言の場合は1が代入される for(var i=0; i<10000; i++) { result += i * num; } console.timeEnd('count2');
計測タイム
count2: 18.860ms
この例では、OR演算子を活用して「num || 1」と記述することで、numが未宣言の場合に「1」が代入されます。これによりtry…catch文を使う必要性はなくなりました。結果的に処理時間も18.860msとなり、先ほどよりも大きくパフォーマンスが向上していることが分かります。
重要なのは、try…catch文を必ず使わないとエラー処理を回避できないのかどうかを見極めることです。
まとめ
今回は、エラー処理を簡潔にできるtry…catch文を学習しました!学習のポイントを振り返ってみましょう!
- try…catch文で予想外のエラー処理を回避できる
- throwで意図的にエラーを発生させることができる
- finally文を使うと必ず実行する処理を記述できる
- 例外処理に合わせたエラーオブジェクトを利用するとデバッグが効率的
以上の内容を再確認し、ぜひ自分のプログラムに生かし学習を進めてください!