6. 非同期処理¶
この章では、非同期処理の実装や制御に関連する方法を説明する。
6.1. Deferredによる非同期処理制御¶
6.1.1. 概要¶
| 利用ライブラリ | サンプル | 参考ページ | 
|---|---|---|
| jQuery | - | Deferred Object | jQuery | 
6.1.2. 非同期処理のネスト解消¶
| サンプル | 
|---|
| 1秒毎にメッセージを出力 | 
setTimeout(function () {
  $('#deferred-area').append('<p>1</p>');
  setTimeout(function () {
    $('#deferred-area').append('<p>2</p>');
    setTimeout(function () {
      $('#deferred-area').append('<p>3</p>');
    }, 1000);
  }, 1000);
}, 1000);
| サンプル | 
|---|
| 1秒毎にメッセージを出力(Deferred適用) | 
// (1)
var outputMessage1 = function () {
  // (2)
  var dfd = new $.Deferred();
  setTimeout(function () {
    $('#deferred-area').append('<p>1</p>');
    // (3)
    dfd.resolve();
  }, 1000);
  // (4)
  return dfd.promise();
};
// (5)
var outputMessage2 = function () {
  /* omitted */
};
// (5)
var outputMessage3 = function () {
  /* omitted */
};
// (6)
outputMessage1()
.then(outputMessage2)
.then(outputMessage3)
// (7)
.catch(function () {
  alert('failed!');
});
| 項番 | 説明 | 
|---|---|
(1) 
 | 
1秒後にメッセージ「1」を出力する関数。 
 | 
(2) 
 | 
Deferredオブジェクトを生成する。 
 | 
(3) 
 | 
非同期処理内で最後に非同期処理の実行状態を変更する。 
非同期処理の実行状態については後述する。 
 | 
(4) 
 | 
Promiseオブジェクトを返却する。 
 | 
(5) 
 | 
メッセージ「2」「3」を出力する関数。 
outputMessage1と同様のため、実装は省略する。 
 | 
(6) 
 | 
非同期処理完了後の後続処理をthenで設定する。 
直前の非同期処理が正常に完了すると、thenに設定されたコールバックが実行される。 
 | 
(7) 
 | 
各非同期処理のエラーハンドリングをcatchで設定する。 
catchについては 非同期処理のエラーハンドリング で説明する。 
 | 
Note
Deferredオブジェクトを生成すると、内部で非同期処理の実行状態を管理するためのPromiseオブジェクトが生成される。このPromiseオブジェクトには次の3つの実行状態がある。
- pending (初期状態 = 実行中)
 - fulfilled (成功状態 = 正常終了)
 - rejected (失敗状態 = 異常終了)
 
DeferredオブジェクトはこのPromiseオブジェクトの状態を2つのメソッド Deferred.resolve(), Deferred.reject()によってそれぞれfulfilled (成功), rejected (失敗)の状態に変更できる。
この状態変化に応じて、以下のようにthenや catchで設定されたコールバックが実行される仕組みとなっている。
- fulfilledの状態 → then(successCallback)が実行
 - rejectedの状態 → catch(errorCallback)が実行
 
6.1.3. 非同期処理のエラーハンドリング¶
| サンプル | 
|---|
| 非同期処理のエラーハンドリング | 
// (1)
var random = function () {
  return Math.floor(Math.random() * 2) === 1 ? true : false;
};
var async = function () {
  var dfd = new $.Deferred();
  setTimeout(function () {
    var result = random();
    $('#deferred-area').append('<p>ランダム処理の結果は' + result + '.</p>');
    if (result) {
      // (2)
      dfd.resolve('resolve');
    } else {
      // (2)
      dfd.reject('reject');
    }
  }, 1000);
  return dfd.promise();
};
// (3)
var success = function (arg) {
  $('#deferred-area').append('<p>' + arg + 'が実行されました。成功です。</p>');
};
// (3)
var failure = function (arg) {
  $('#deferred-area').append('<p>' + arg + 'が実行されました。失敗です。</p>');
};
async()
.then(success)
// (4)
.catch(failure);
| 項番 | 説明 | 
|---|---|
(1) 
 | 
trueかfalseをランダムに返却する関数。 
 | 
(2) 
 | 
ランダム関数の結果に応じてPromiseオブジェクトの状態を変更する。引数には文字列を設定する。 
 | 
(3) 
 | 
resolveまたはrejectから文字列を受け取り、メッセージを出力する関数。 
 | 
(4) 
 | 
Promiseオブジェクトの状態がrejectedになると、catchで設定したコールバックを実行する。 
 | 
Note
resolveとrejectは引数を設定でき、それぞれthenとcatchのコールバックで受け取れる。
Note
catchをチェーンしない場合、thenのコールバック内部で発生した例外をthenの外側で捕捉できない問題が発生する。
jQuery公式のUpgradeGuideでは 「.catch()をPromiseチェーンの最後に追加することを強く推奨」 としている。
Warning
thenと catchの他に doneと failによる後続処理やエラーハンドリングの方法もあるが、本ガイドラインでは以下の理由によりdoneと failの利用を非推奨としている。
- Promise標準のPromises/A+、ES6に準拠しておらず、後方互換性を保つために古い挙動を保持していることから今後非推奨となる可能性がある。
 
6.1.4. 非同期処理の待ち合わせ¶
| サンプル | 
|---|
| 非同期処理の待ち合わせ | 
// (1)
var asyncFuncA = function () {
  var dfd = new $.Deferred();
  setTimeout(function () {
    $('#deferred-area').append('<p>Function A が終了しました</p>');
    dfd.resolve();
  }, 1000);
  return dfd.promise();
};
// (2)
var asyncFuncB = function () {
  /* omitted */
};
// (2)
var asyncFuncC = function () {
  /* omitted */
};
// (3)
var outputMessage = function () {
  $('#deferred-area').append('<p>全ての処理が終了しました</p>');
};
// (4)
$.when(asyncFuncA(), asyncFuncB(), asyncFuncC())
.then(outputMessage)
.catch(function () {
  alert('failed!');
});
| 項番 | 説明 | 
|---|---|
(1) 
 | 
非同期処理終了後にメッセージを出力する関数。 
 | 
(2) 
 | 
非同期処理終了後にメッセージを出力する関数。 
asyncFuncAと同様のため、実装は省略する。 
 | 
(3) 
 | 
メッセージを出力する関数。 
 | 
(4) 
 | 
各非同期処理完了後の後続処理をthenで設定する。 
 | 
Note
whenに複数の非同期処理を渡すと、pendingの状態を持つPromiseオブジェクトが返却される。Promiseオブジェクトは各非同期処理の状態を管理しており、全ての非同期処理がfulfilledになるとthenで設定したコールバックを実行する。
ただし、いずれかの非同期処理の状態が1つでもrejectedになると、実行中の非同期処理の完了を待たず、catchで設定したコールバックが実行される。その際、 実行中の非同期処理が中断されない ことに注意が必要である。
6.1.5. 非同期通信へのDeferred適用¶
| サンプル | 
|---|
| 非同期通信へのDeferred適用 | 
var doAjax = function (path) {
  var dfd = new $.Deferred();
  // (1)
  $.ajax({
    'type' : 'GET',
    'url' : path,
    'dataType' : 'json'
  })
  // (2)
  .then(function (data) {
    $('#deferred-area').append('<p>' + path + 'の取得に成功しました。</p>');
    dfd.resolve([path, data]);
  })
  // (3)
  .catch(function () {
    $('#deferred-area').append('<p>' + path + 'の取得に失敗しました。</p>');
    dfd.reject(path);
  });
  return dfd.promise();
};
// (4)
var showData = function (data) {
  for (var n = 0, len = data.length; n < len; n++) {
    $('#deferred-area').append(data[n].text + ' : ' + data[n].value + '<br />');
  }
};
// (5)
var successCallback = function (array) {
  $('#deferred-area').append('<p>' + array[0] + 'の読み込みが成功しました。</p>');
  showData(array[1]);
};
// (6)
var errorCallback = function (path) {
  $('#deferred-area').append('<p>' + path + 'の読み込みに失敗しました。</p>');
};
// (7)
doAjax('data/dataA.json')
.then(successCallback)
.catch(errorCallback);
// (7)
doAjax('data/dataB_dummy.json')
.then(successCallback)
.catch(errorCallback);
| 項番 | 説明 | 
|---|---|
(1) 
 | 
非同期通信を実行する。 
 | 
(2) 
 | 
非同期通信が成功した場合に以下を実行する。 
1. メッセージを出力する。 
2. resolveの引数にファイルパスとjsonデータを含む配列を設定し実行する。 
 | 
(3) 
 | 
非同期通信が失敗した場合に以下を実行する。 
1. メッセージを出力する。 
2. rejectの引数にファイルパスを設定し実行する。 
 | 
(4) 
 | 
非同期通信で取得したjsonデータを画面に出力する関数。引数にjsonデータを受け取る。 
 | 
(5) 
 | 
メッセージとリストを出力する関数。引数にファイルパスとjsonデータを含む配列を受け取る。 
 | 
(6) 
 | 
メッセージを出力する関数。引数にファイルパスを受け取る。 
 | 
(7) 
 | 
引数にファイルパスを設定し、非同期処理を実行する。 
非同期通信失敗時の挙動が確認できるよう、dataB_dummy.jsonは存在しないファイルパスを指定する。 
 | 
Note
ajaxはThe jQuery XMLHttpRequest (以下、jqXHRとする)オブジェクトを返却する。jqXHRはPromiseインターフェースを実装しているため、thenやcatchをチェーンさせることができる。ajaxの詳細な利用方法については Ajaxを利用した連携 を参照すること。
