PSI Labs RSS feed

任意の数の ajax リクエストを実行し、全てのリクエスト完了後に何か処理をしたい

こんにちは、tomita です。

以下の仕様を満たす javascript の実装を考えます。

  • test.php?param=1~5 にリクエストを投げる(※param値は将来増減する可能性アリ)
  • すべてのリクエストを投げ終えたあとで「全て完了しました」を console.log に出力

このような仕様を満たすには jQuery.Deffered を使うといいとのことでしたので、実際に試してみました。

jQuery.Deffered を使わない場合

の前に、jQuery.Deffered を使わず、ノープランで実装してみます。

$(function(){
  "use strict";
  var url = "test.php";
  $.ajax({ url:url, data:{param:1}}).done(function(res){
    console.log( res + ":done");
    $.ajax({ url:url, data:{param:2}}).done(function(res){
      console.log( res + ":done");
      $.ajax({ url:url, data:{param:3}}).done(function(res){
        console.log( res + ":done");
        $.ajax({ url:url, data:{param:4}}).done(function(res){
          console.log( res + ":done");
          $.ajax({ url:url, data:{param:5}}).done(function(res){
            console.log( res + ":done");
            console.log( "全て完了!" );
          });
        });
      });
    });
  });
});

問題点はいくつかあって、ソースがネストしまくって見にくい、リクエスト数を増減させたい場合に修正が大量発生する、リクエストが失敗した場合に途中で止まる、と言った具合です。メンテナンス性も悪く使えたもんじゃありません。

jQuery.Deffered を使う場合(ダメ版)

ということで jQuery.Deffered を使って実装しなおしてみます。送りたいリクエストの数だけ $.ajax オブジェクトを作成して $.when に渡す実装です。

$(function(){

  "use strict";
  var url = "test.php";
  var ajax_list = [];
  var i=1, request_count=5;

  // $.ajax をリクエスト数だけ作成
  for(; i<=request_count; i++){
    var $ajax = $.ajax({url:url, data:{param:i}}).done(function(res){
      console.log( res + ":done");
    });
    ajax_list.push( $ajax );
  }

  // $.when に渡してまとめて処理
  $.when.apply(null, ajax_list).done(function(){
    console.log( "全て完了しました" );
  });
  $.when.apply(null, ajax_list).fail(function(){
    console.log( "どこかで失敗しました" );
  });

});

以下、実行した結果です。リクエスト終了後に $.when.apply.done が実行されており仕様を満たしています。リクエスト数を変更したい場合も request_count の値を変更するだけなので、メンテナンス性も高いです。

1:done
2:done
3:done
4:done
5:done
全て完了しました

ただしこれも問題があって、test.php へのリクエストが何かの拍子で失敗した場合(※503 Service Unavailable など) $.when.apply.fail が実行され、$.when.apply.done は実行されません。以下は test.php?param=3 が失敗した場合の実行結果です。リクエストは全て実行されますが、$.when.apply.done が実行されず仕様を満たしません。

1:done
2:done
どこかで失敗しました
4:done
5:done

jQuery.Deffered を使う場合(OK版)

ということでリクエスト成功/失敗に関わらず $.when.apply.done が呼ばれるよう、$.ajaxjQuery.Deffered でラップして、常に resolve を返すように改造します。
また、呼び出し元でエラー検知ができるよう、resolveWith を使って引数も渡してあげます。具体的には以下のような実装になります。

$(function(){

  "use strict";
  
  // jQuery.ajax を jQuery.Defferd でラップした関数
  var myAjax = function(opt){
    var $ajax = $.ajax(opt);
    var defer = new $.Deferred();
    $ajax.done(function(data, status, $ajax){
      // resolve ではなく resolveWith を使って引数を渡す
      defer.resolveWith(this, arguments);
    });
    $ajax.fail(function(data, status, $ajax){
      // リクエストが失敗しても resolve を設定
      defer.resolveWith(this, arguments);
    });
    // $ajax を defer.promise() で上書きしてしまう
    return $.extend({}, $ajax, defer.promise());
  };

  var url = "test.php";
  var deffered_list = [];
  var i=1, length=5;

  // $.ajax をリクエスト数だけ作成
  for(; i<=length; i++){
    var $ajax = myAjax({url:url, data:{param:i}, context:{i:i}}).done(function(res, status){
      // status を見て処理分岐
      if ( status === "success" ) {
        // 成功時の処理
        console.log( this.i + ":done");
      } else {
        // 失敗時の処理
        console.log( this.i + ":failed");
      }
    });
    deffered_list.push( $ajax );
  }

  // $.when に渡してまとめて処理
  $.when.apply(null, deffered_list).done(function(){
    console.log( "全て完了しました" );
  });
  $.when.apply(null, deffered_list).fail(function(){
    console.log( "どこかで失敗しました" );
  });

});

先ずは正常なリクエストの場合です。5つのリクエストが終了した後 $.when.apply.done が呼ばれるので仕様を満たしています。

1:done
2:done
3:done
4:done
5:done
全て完了しました

次に test.php?param=3 が失敗した場合です。これもきちんと5つのリクエストを待ってから $.when.apply.done が呼ばれているので仕様を満たします。

1:done
2:done
3:failed
4:done
5:done
全て完了しました

jQuery.Deffered は本当に便利ですね… それでは~