uhyohyo.net

JavaScript初級者から中級者になろう

九章第八回 例外処理

例外とは

今回は例外の解説です。例外とは何かというと、簡単にいうとエラーのことです。ふつう、エラーが出るとプログラムはそこで強制的に終了してしまいます。しかし、それだと困る場面があります。そこで、エラーが発生しても対応できる方法があります。

throw文

そこでまず解説するのが、throw文です。throwとは英語で「投げる」という意味です。throwで何かが投げられると、エラーが発生したことになります。

a=3;
throw a;
console.log(a);
          

この例では、aを投げています。throwでエラーが発生したので、その後のconsole.logは実行されません。

このようにして、エラーを発生させることができました。ところで、throwで何を投げてもエラーが発生するわけですが、それでは何を投げるのがよいのでしょう。

Errorオブジェクト

ここで登場するのがErrorオブジェクトです。これはつまりErrorのインスタンスで、その名の通りエラーを表すオブジェクトで、これを投げるのがよいです。

Errorのインスタンスにはmessageというプロパティがあり、これがエラーメッセージを表します。

var err = new Error();
err.message = "エラーが発生しました。";
          throw err;
        

このようにして、ちゃんと例外が発生したときにエラーメッセージも表すことができ、親切です。

また、いちいちmessageに代入することなく、次のようにできます。

throw new Error("エラーが発生しました。");

これはどういうことかというと、前と決定的に違うのはErrorに引数があるということです。実は、引数を指定すると、コンストラクタErrorの働きによってmessageに代入してくれます。Errorを自分で作るとすると、

functin Error(mes){
  this.message = mes;
}

という感じですね。

これを使うことで変数に代入してからプロパティに代入する必要もなくなり、throw文に渡すときに直接newでインスタンスを作ることができるようになりました。もっとも、messageに自分で代入する方法でも、throw文に渡すときに処理する方法はあります。詳しくは解説しませんが、

throw (function(){var err=new Error();err.message="エラーが発生しました。";return err})();
        

という感じです。一章第四回に出てきた無名関数を利用した方法です。まあしかし、こんな回りくどい方法を使う必要はありません。素直にErrorコンストラクタにエラーメッセージを渡しましょう。

try〜catch文

さて、これでエラーの発生のさせ方は分かりました。しかし、ただエラーを出すだけでは例外処理とはいえません。もっと凄いことができるのです。

今まで見てきたように、通常エラーが発生したら実行は止まります。しかし、JavaScriptでエラーの発生を検知して処理するという方法があります。それは、try〜catch文です。これは次のように書きます。

try{
処理1
}
catch(e){
処理2
}
        

try{ 〜 }catch(e){ 〜 }の中にそれぞれ処理が書いてあります。

これはどのように実行されるかというと、まずtryの中の処理1が実行されます。ここで、エラーが起きなければ処理2は実行されません。

もしここでエラーが起きたら、処理1はそこで中断されます。そして、catch中の処理2が実行されます。throw(投げる)したエラーをcatch(受け止める)するという感じです。

処理2はcatch(e){ }の中に書いてありますが、この (e) とは何でしょう。実は、このeというのは変数名なので、別にeでなくても構いません。エラーが起きて処理2が実行されるとき、この変数に投げられたものが代入されます。前述の通り、大抵はErrorオブジェクトですね。

それでは試してみましょう。

try{
  a;
}catch(e){
  console.log(e);
}
        

これを実行するとログが表示されます。ブラウザによって多少違うかもしれませんが、「ReferenceError」ということが書いてあると思います。

これは何が起きたかというと、tryの中にある文はa;の一文だけです。こういう場合の挙動は九章第六回で解説したもので、特に意味はないが、とりあえずaが参照されるのでした。

しかし、aという変数はないので、ここでエラーが発生したわけです。

実は、このように自分でthrowで投げたエラー以外でもcatchすることができます。その場合でも、throwのときと同じようにちゃんと何かが投げられています。catchの中では、それをconsole.logで表示しています。

さて、投げられてcatchしたこのeは、実はオブジェクトです。

try{
  a;
}catch(e){
  console.log(e instanceof Error);
}
        

このようにしてみると、これも前述のErrorのインスタンスであるということが分かります。

また、

console.log(e instanceof ReferenceError);

がtrueとなることから、実はeはReferenceErrorというオブジェクトのインスタンスでもあったのです。実はこのReferenceErrorというのはErrorを継承しています。JavaScriptで発生するエラーは数種類に大別されていて、それぞれに対応するオブジェクトがあり、それぞれErrorを継承しています。これはちょうど「敵」から「ドラゴン」「魔法使い」などが派生していくさまと同じです。

同様に、もちろん自分でthrowすることも可能です。

try{
  throw new Error("エラーが発生しましたか。");
}catch(e){
  console.log(e);
}
        

さて、try〜catchを利用したもう少し実用的な例を考えてみましょう。九章第六回の次のサンプルを持ってきました。

var obj = {
  _a : 0,
  get a(){return this._a; },
  set a(n){
    if(n===true || n===false){
      this._a = n;
    }
  }
};
        

このオブジェクトobjのプロパティaに、数値以外は代入できなくするというサンプルでした。

例えば、代入する値をユーザーが入力する場合を考えてみましょう。具体的な方法は二章第十二回などで解説したので、今回はわりと抽象的な説明です。

入力した値をこのobjのプロパティに代入するわけですが、もし代入に失敗した場合、ユーザーに教えてあげたいとします。簡単なのは、

var obj = {
  _a : 0,
  get a(){return this._a; },
  set a(n){
    if(n===true || n===false){
      this._a = n;
    }else{
      alert("代入できません!");
    }
  }
};
        

のようにしてアラートを出すなどです。

しかし、アラートというのはユーザーからしてみたら不便なものです。アラートを消すまでは他の操作ができないからです。そこで、これを使わない方法を考えましょう。

ここで登場するのが例外処理です。この場合、セッタ内で代入できないときにやるべきことは、throwでエラーを投げることです。こうすることで、関数外に情報を伝えることができるのです。

var obj = {
  _a : 0,
  get a(){return this._a; },
  set a(n){
    if(n===true || n===false){
      this._a = n;
    }else{
      throw new Error("代入できません!");
    }
  }
};
        

このままでも使えないことはありません。不正な値を代入しようとするとエラーを起こすという厄介なオブジェクトのできあがりです。

このようにして、エラーを投げることで関数外、しいてはプログラム全体に情報を使えることができました。これをうまく扱うのが今回説明してきたtry〜catch文です。

このオブジェクトを使う部分をtryの中に入れておけば、不正な値が代入されたときエラーが投げられ、それをcatchできるというわけです。つまり、このようになります。

//↓↓↓プログラム全体の流れ↓↓↓

try{
  var i = ユーザーの入力を取得してくれる関数();

  obj.a = i;	//←エラーを投げるかもしれないセッタを使用

  ユーザーの入力を使用する関数(obj);
}catch(e){
  注意を表示してくれる関数();
}
        

ここで登場しているobjは、セッタを利用して不正な値を代入しようとするとエラーを投げるものです。

これはいい感じに役割がわかれています。「プログラム本体」(このプログラム全体)では、処理の流れを決めています。まずユーザーの入力を取得して、それを代入して、使用して何らかの処理をするという流れになっています。

ここで、架空の関数が3つ登場しています。それぞれの機能をそれぞれ1つの関数にまとめることができたのです。そして、それぞれの関数は他の機能に干渉しません。抽象的で分かりにくいかもしれませんが、try〜catch文にはこういった利点があります。

また、ここでエラーをcatchしたら無条件で「注意を表示してくれる関数」を呼んでいますが、万が一違う原因でエラーが起きていたらどうしましょう。

投げられたエラーのオブジェクトを判別する必要が出てきます。その方法として、Errorを継承したオブジェクトを自分で作ってしまうという方法があります。

function myError(){
  Error.apply(this,arguments);
}
myError.prototype = new Error;

var obj = {
  _a : 0,
  get a(){return this._a; },
  set a(n){
    if(n===true || n===false){
      this._a = n;
    }else{
      throw new myError("代入できません!");
    }
  }
};
try{
  obj.a="あああ";
}catch(e){
  if(e instanceof myError){
    console.log("代入できないエラーが発生しました。");
  }
}
        

こうするとメッセージが表示されます。

ちなみにこの時、e.messageに実は"代入できません!"が代入されていません。これは、コンストラクタErrorが呼び出されたときに、new演算子がついていない場合でも内部でコンストラクタを作成して返すという仕様になっているからです。すなわち、Error.applyでError関数を呼んだとき、Error関数は自身がnewで呼ばれたかどうかを判別して、違った場合はthis(applyで変えられている)をいじるのではなくnewで新しいインスタンスを作成してそれを返り値として返すからです。そのような仕様になっている理由は、new Error("メッセージ")の代わりにError("メッセージ")を書けるようにするためでしょう。メッセージがどうしても必要な場合は、自分で代入するのがよいでしょう。

finally文

さて、実はfinally文というのがあります。これは、try〜catch構文の後に付けることで、エラーが起きた場合でも起きなかった場合でも最終的に実行される部分となります。

try{

}
catch(e){

}
finally{

}
        

という形です。今までのサンプルを見て分かる通り、finallyはなくても構いません。また、catchを省略してtry{ 〜 }finally{ 〜 }とすることが可能です。この場合は、tryの中でエラーが投げられた場合、そのままfinallyに移行して終了ということになります。