制御フローとエラー処理

This article is in need of a technical review.

This article is in need of an editorial review.

JavaScript は Web ページに多様なインタラクティビティをもたらすコンパクトな文 (statement) の集合体、特に制御フロー文をサポートしています。本章ではこれらの文の概要を説明します。

JavaScript リファレンスにはこの章で紹介した文についての完全な詳細が載っています。また、JavaScript のコードではセミコロン (;) 文字で文を区切ります。

あらゆる式は、文でもあります。式に関する詳細については式と演算子を参照ください。

ブロック文

たいていの場合、ブロック文は文のグループ化に使います。ブロックは、1 組の波括弧で区切られます :

{
  statement_1;
  statement_2;
  .
  .
  .
  statement_n;
}

用例

ブロック文は一般に制御フロー文(例えば ifforwhile など)で用いられます。

while (x < 10) {
  x++;
}

ここでは { x++; } がブロック文となります。

重要 : ECMAScript 6 以前の JavaScript にはブロックスコープがありません。ブロック内で導入された変数のスコープは、そのブロックがある関数やスクリプトになります。またそれらの変数を設定した影響は、そのブロックを越えて持続します。つまり、ブロック文はスコープを定義しないということです。「独立した」ブロックも正しい構文ですが、C や Java のブロックで提供されるものとは異なる結果をもたらします。例えば :

var x = 1;
{
  var x = 2;
}
console.log(x); // 2 を出力

ブロック内の var x 文はブロックの前の var x 文と同じスコープ内にあるため、この例では 2 が出力されます。C や Java では、同様のコードで 1 が出力されます。

ECMAScript 6 からは、let 文による変数宣言がブロックスコープとなります。詳しくは let 文の参考ページをご覧ください。

条件文

条件文は、指定した条件が true ならば実行される命令の集まりです。JavaScript は if...elseswitch の 2 つの条件文をサポートしています。

if...else

論理条件が true の場合に、文を実行するには if 文を使用してください。オプションの else 節を使用すると、条件が false の場合にも文を実行することができます。if 文は次のように使用します :

if (condition) {
  statement_1;
} else {
  statement_2;
}

condition には、true か false に評価される式が入ります。どういったものが truefalse として評価されるかについては Boolean オブジェクトをご覧ください。condition が true に評価された場合は statement_1 が実行され、そうでない場合は statement_2 が実行されます。statement_1statement_2 はどのような文でもかまいません。if をさらに入れ子にすることもできます。

次のように else if を使用して文を重ねることで、複数の条件を順々にテストすることができます :

if (condition_1) {
  statement_1;
} else if (condition_2) {
  statement_2;
} else if (condition_n) {
  statement_n;
} else {
  statement_last;
}

複数の文を実行するには、ブロック文 ({ ... }) を使用してその文をグループ化してください。一般に、常にブロック文を使用するのが優れた方法です。特に if 文を入れ子にしたコードで有効です :

if (condition) {
  statement_1_runs_if_condition_is_true;
  statement_2_runs_if_condition_is_true;
} else {
  statement_3_runs_if_condition_is_false;
  statement_4_runs_if_condition_is_false;
}

条件式内で単純な代入を行わないでください。コードを一見した際に、代入を等値条件と見間違えるおそれがあるためです。例えば、次のコードにあげるような使用法はやめてください :

if (x = y) {
  /* ここに文が来る */
}

条件式で代入を行う必要がある場合、一般的な方法は代入式をさらに丸括弧でくくることです。例えば :

if ((x = y)) {
  /* ここに文が来る */
}

false と評価される値

以下の値は false に評価されます :

  • false
  • undefined
  • null
  • 0
  • NaN
  • 空の文字列 ("")

上記以外の、オブジェクトを含むすべての値は、条件文に渡されると true に評価されます。

プリミティブな真偽値の truefalse を、Boolean オブジェクトの true や false という値と混同しないでください。例えば :

var b = new Boolean(false);
if (b) // この条件は true に評価される

用例

次の例で、関数 checkDataText オブジェクトに含まれている文字数が 3 である場合に true を返し、そうでない場合はアラートを表示して false を返します。

function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert("Enter exactly three characters. " +
    document.form1.threeChar.value + " is not valid.");
    return false;
  }
}

switch

switch 文を使うと、プログラムは式を評価し、その式の値を case ラベルと照合します。マッチすると、プログラムはそのラベルに関連付けられた文を実行します。switch 文は次のようになります :

switch (expression) {
  case label_1:
    statements_1
    [break;]
  case label_2:
    statements_2
    [break;]
    ...
  default:
    statements_def
    [break;]
}

プログラムはまず、式の値にマッチするラベルを持つ case 節を探します。そして制御をその節に移し、関連付けられた文を実行します。マッチするラベルがない場合、プログラムはオプションの default 節を探し、存在する場合は default 節に制御を移し、関連付けられた文を実行します。default 節がない場合、プログラムは switch 文の最後まで、後に続く文を実行し続けます。慣例により、default 節は最後の節に置きますが、そうしなければいけないわけではありません。

case に関連付けられたオプションの break 文は、マッチした文を実行したら switch 文から抜けて、確実に switch 文以後の文を実行するためのものです。break を省略した場合、プログラムはその switch 文内の次の文から実行を続けます。

用例

次の例では、fruittype が "Bananas" に評価された場合は case "Bananas" にマッチして、それに関連付けられた文を実行します。break 文に出くわすとプログラムは switch から抜けて、switch の後に続く文を実行します。break を省略すると、case "Cherries" の文も実行されます。

switch (fruittype) {
  case "Oranges":
    console.log("Oranges are $0.59 a pound.");
    break;
  case "Apples":
    console.log("Apples are $0.32 a pound.");
    break;
  case "Bananas":
    console.log("Bananas are $0.48 a pound.");
    break;
  case "Cherries":
    console.log("Cherries are $3.00 a pound.");
    break;
  case "Mangoes":
    console.log("Mangoes are $0.56 a pound.");
    break;
  case "Papayas":
    console.log("Mangoes and papayas are $2.79 a pound.");
    break;
  default:
   console.log("Sorry, we are out of " + fruittype + ".");
}
console.log("Is there anything else you'd like?");

例外処理文

throw 文を使用して例外を発生させる(例外をスローする)ことができます。また、try...catch 文を使用して例外を処理することができます。

例外の種類

JavaScript では、ほぼどのようなオブジェクトでも例外としてスローすることができます。とはいえ、必ずしもスローされるオブジェクトすべてが同等に作られているわけではありません。数値や文字列をエラーとしてスローする方法がよく用いられますが、こうした用途のために特別に作られた例外データ型を使用した方がより効率的な場合もあります :

throw

throw 文は、例外をスローするために使用します。例外をスローするには、スローしたい値を含む式を指定してください :

throw expression;

特定の型の式だけではなく、あらゆる式をスローすることができます。下記のコードでは、さまざまな型の例外をスローしています :

throw "Error2";   // String type
throw 42;         // Number type
throw true;       // Boolean type
throw {toString: function() { return "I'm an object!"; } };

注記 : 例外をスローする際にオブジェクトを指定することができます。そして、catch ブロックでそのオブジェクトのプロパティを参照できます。次の例では、UserException という種類の myUserException オブジェクトを作成します。

// UserException というオブジェクト型を作成
function UserException (message){
  this.message=message;
  this.name="UserException";
}

// 文字列として使用されるとき(例 : エラーコンソール上)に
// 例外を整形する
UserException.prototype.toString = function (){
  return this.name + ': "' + this.message + '"';
}

// UserException のインスタンスを作成し、それをスローする
throw new UserException("Value too high");

try...catch

try...catch 文はテストしたい文のブロックを指定し、さらにスローされる例外に対する 1 つ以上の対処方法を指定します。例外がスローされると、try...catch 文がそれを受け取ります。

try...catch 文は 1 つの try ブロックと 0 個以上の catch ブロックで構成されます。try ブロックは 1 つ以上の文を含み、catch ブロックは try ブロックで例外がスローされた場合の処理を指定する文が含まれます。要するに、成功した場合に実行したい try ブロックと、失敗した場合に制御を移行させたい catch ブロックで構成されています。try ブロック内(もしくは try ブロック内から呼び出された関数内)のいずれかの文が例外をスローすると、制御はすぐに catch ブロックに移ります。try ブロックで例外がスローされなかった場合、catch ブロックはスキップされます。finally ブロックは try および catch ブロックを実行した後に実行しますが、try...catch 文の後に続く文より先に実行されます。

次の例では try...catch 文を使用しています。この例では渡された値に基づいて、配列から月の名前を取り出す関数を実行します。値に対応する月の数字 (1-12) が無い場合は "InvalidMonthNo" という値を持つ例外がスローされ、catch ブロックの中の文は monthName という変数に unknown という値をセットします。

function getMonthName(mo) {
  mo = mo - 1; // 月の数字を配列のインデックスに合わせる (1=Jan, 12=Dec)
  var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul",
                "Aug","Sep","Oct","Nov","Dec"];
  if (months[mo] != null) {
    return months[mo];
  } else {
    throw "InvalidMonthNo"; //throw キーワードが使われている
  }
}

try { // 実行を試みる文
  monthName = getMonthName(myMonth); // この関数が例外をスローする場合がある
}
catch (e) {
  monthName = "unknown"
  logMyErrors(e); // 例外オブジェクトをエラーハンドラに渡す
}

catch ブロック

catch ブロックを用いることで、try ブロックで生じうるすべての例外を扱うことができます。

catch (catchID) {
  statements
}

catch ブロックには、throw 文で指定される値を保持しておく識別子(上記の構文における catchID)を指定します。スローされた例外についての情報を得るのに、この識別子を使います。JavaScript は catch ブロックに入るときにこの識別子を作成します。識別子は catch ブロックの区間だけ存続します。つまり、catch ブロックの実行が終わると、その識別子はもう使えなくなります。

例えば、次のコードは例外をスローします。例外が生じると、制御が catch ブロックに移ります。

try {
  throw "myException"; // 例外を生成
}
catch (e) {
  // ここには例外を扱う文が入る
  logMyErrors(e); // 例外オブジェクトをエラーハンドラに渡す
}

finally ブロック

finally ブロックは、try および catch ブロックの実行が終わった後に、try...catch 文の後に続く文よりも先に実行される文で構成されます。finally は例外がスローされてもされなくても実行されます。例外がスローされた場合、finally ブロック内の文はたとえ例外処理をする catch ブロックがなくても実行されます。

finally ブロックを使用することで、例外発生時に適切にスクリプトを停止させることができます。例えば、スクリプトで使用していたリソースを解放しなければならないとします。次の例ではファイルを開き、そのファイルを使用する文を実行します (サーバサイド JavaScript ではファイルにアクセスできます)。ファイルを開いている間に例外がスローされると、スクリプトが停止する前に finally ブロックでそのファイルを閉じます。

openMyFile();
try {
  writeMyFile(theData); // ここでエラーがスローされる可能性がある
}catch(e){
  handleError(e); // エラーを受け取り、それを処理する
}finally {
  closeMyFile(); // 常にリソースが閉じられる
}

finally ブロックが値を返す場合、その値は try および catch ブロックの return 文にかかわらず、try-catch-finally 全体が生成する戻り値になります :

function f() {
  try {
    console.log(0);
    throw "bogus";
  } catch(e) {
    console.log(1);
    return true; // この戻り値は、finally ブロックが
                 // 完了するまで保留となる
    console.log(2); // この先は実行されない
  } finally {
    console.log(3);
    return false; // 直前の "return" が上書きされる
    console.log(4); // この先は実行されない
  }
  // ここで "return false" が実行される
  console.log(5); // この先は実行されない
}
f(); // 0, 1, 3 がログに表示され、false が返される

try...catch 文のネスト

1 つ以上の try...catch 文を入れ子にすることができます。内側の try...catch 文に catch ブロックがない場合、囲んでいる try...catch 文の catch ブロックがエラーの照合先としてチェックされます。

Error オブジェクトの活用

エラーの種類に応じてより手の込んだメッセージが得られるように、'name' および 'message' プロパティを使うことができます。'name' はエラーの一般的なクラス(例えば 'DOMException' や 'Error')を表し、一方 'message' は通常、Error オブジェクトを文字列に変換したものより簡潔なメッセージを表します。

独自の例外をスローして、これらのプロパティを有効に活用したい場合(catch ブロックで独自の例外とシステムの例外とを区別したくない場合など)、Error コンストラクタが使えます。例えば :

function doSomethingErrorProne () {
  if (ourCodeMakesAMistake()) {
    throw (new Error('The message'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
....
try {
  doSomethingErrorProne();
}
catch (e) {
  console.log(e.name); // 'Error' とログに表示される
  console.log(e.message); // 'The message'、または
                          // JavaScript のエラーメッセージがログに表示される
}

Promise

ECMAScript 6 から、JavaScript には遅延・非同期処理の制御フローを可能にする Promise オブジェクトのサポートが追加されました。

Promise は以下にある状態の一つを取ります :

  • pending (処理待ち / 未決): 初期状態で、承認も拒否もされていない状態。
  • fulfilled (承認 / 可決): 処理が成功した状態。
  • rejected (拒否 / 否決): 処理が失敗した状態。
  • settled (解決): Promise オブジェクトが処理待ちでなく、承認か拒否された状態。

XHR を使った画像のロード

画像のロードに PromiseXMLHttpRequest を使った分かりやすい例が MDN GitHub promise-test リポジトリで利用できます。実際の動きも確認できます。それぞれのステップにはコメントが付けられ、Promise と XHR の基本概念をじっくりと観察することができます。 下記にあるのはコメントのないバージョンですが、Promise の制御フローが手に取るようにわかると思います。 :

function imgLoad(url) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.responseType = 'blob';
    request.onload = function() {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(Error('Image didn\'t load successfully; error code:' 
                     + request.statusText));
      }
    };
    request.onerror = function() {
      reject(Error('There was a network error.'));
    };
    request.send();
  });
}

詳細については、Promise オブジェクトのリファレンスページをご覧ください。

ドキュメントのタグと貢献者

Contributors to this page: x2357, teoli, ethertank, yyss, Electrolysis
最終更新者: x2357,
サイドバーを隠す