Edited at

結果の順番を保証して、且つ『並列』でAjax通信を行う方法

More than 3 years have passed since last update.


複数の非同期通信をする場合

適当に作るとこんな感じ。※変数宣言などアンチパターンなのはご愛嬌

var request = [

{ url: 'hoge00.json', params: { hoge: 123, huga: 456 } },
{ url: 'hoge01.json', params: { hoge: 246, huga: 912 } },
{ url: 'hoge02.json', params: { hoge: 369, huga: 1368 } },
{ url: 'hoge03.json', params: { hoge: 492, huga: 1824 } },
{ url: 'hoge04.json', params: { hoge: 615, huga: 2280 } }
];

var results = [];
var doneCount = 0;

for (var i = 0; i <= request.length; i++) {
$.ajax({
url: request[i].url,
data: request[i].params,
type: 'POST',
dataType: 'json',
success: function (responce) {
results.push(responce);
doneCount++;
if (doneCount === resqest.length) {
allDone();
}
}
});
}

function allDone () {
results; // => 結果の順番は通信が成功した順番
};

配列requestに格納したURLの順番と結果の順番は同じとは限らない。

初心者がよくハマるポイントだけど、ほぼ一斉に非同期通信を行い($.ajaxメソッドを実行し)、通信が成功した順番にsuccessコールバックが実行されるので、その成功した結果の順番にresultsがプッシュされるからである。


こんな感じにできたら理想的

var request = [

{ url: 'hoge00.json', params: { hoge: 123, huga: 456 } },
{ url: 'hoge01.json', params: { hoge: 246, huga: 912 } },
{ url: 'hoge02.json', params: { hoge: 369, huga: 1368 } },
{ url: 'hoge03.json', params: { hoge: 492, huga: 1824 } },
{ url: 'hoge04.json', params: { hoge: 615, huga: 2280 } }
];

var jqXHR = $.multiAjax({
request: request,
type: 'POST',
dataType: 'json'
});

jqXHR.done(function (responce) {
responce; // => [00の結果, 01の結果, 02の結果, 03の結果, 04の結果]
});


上記を実現するには


直列処理ならば...

リクエストをひとつひとつ処理していく方法は、結構いろんなところで紹介されている。

基本的にはひとつずつAjax通信をして、そのコールバックで次のAjax通信を行なっている。もしくはasyncfalseにして、非同期通信でなく完全な同期通信をしてしまう方法。これなら結果の順番は絶対変わらない。

でも、やりたいのはそうじゃなくて、通信を同時に一気に行いたい。通信時間短縮のためには『並列』というのが大事。それに加えて結果の順番も保証してほしい。


$.whenを使えば意外と簡単に実現できる。

var request = [

{ url: 'hoge00.json', params: { hoge: 123, huga: 456 } },
{ url: 'hoge01.json', params: { hoge: 246, huga: 912 } },
{ url: 'hoge02.json', params: { hoge: 369, huga: 1368 } },
{ url: 'hoge03.json', params: { hoge: 492, huga: 1824 } },
{ url: 'hoge04.json', params: { hoge: 615, huga: 2280 } }
];

var jqXHRList = [];

for (var i = 0; i <= request.length; i++) {
jqXHRList.push($.ajax({ // $.ajaxの戻り値を配列に格納
url: request[i].url,
data: request[i].params,
type: 'POST',
dataType: 'json'
}));
}

// $.when関数を利用する
// $.whenは可変長引数を取るので、apllyメソッドを利用して配列で渡せるようにする
// $.whenのコンテキスト(applyの第一引数)はjQueryである必要があるので $ を渡す
$.when.apply($, jqXHRList).done(function () {
var json = [];
var statuses = [];
var jqXHRResultList = [];
// 結果は仮引数に可変長で入る **順番は保証されている**
// 取り出すには arguments から取り出す
// さらにそれぞれには [data, textStatus, jqXHR] の配列になっている
for (var i = 0; i < arguments.length; i++) {
var result = arguments[i];
json.push(result[0]);
statuses.push(result[1]);
jqXHRResultList.push(result[3]);
}

json;// => リクエストの配列と同じ順番で結果を参照できる

});

こんなロジックで、先に記した理想に沿ったプラグインにするなど、いろいろ作ればいい。


要するに$.when.apply便利

Ajaxに限らずアニメーションなども、このテクニックを使えば並列の処理が行える。

読み込む外部ファイルやアニメーション要素の数が動的に変化する場合、$.whenのまま使うと可変長引数で渡すときに戸惑うが、applyを使えば第一引数にさえ気をつければ簡単だ。ついでに結果は順番が保証されている。


仕様上の注意

配列の中身が一個だと結果が配列でなくなるので注意

$.when.apply($, [req, req, req]).done(function () {

arguments; // => [[data, status, jqXHR], [data, status, jqXHR], [data, status, jqXHR]]
});
$.when.apply($, [req]).done(function () {
arguments; // => [data, status, jqXHR]
});

なんともありがた迷惑な仕様…。配列の個数が1以下になる可能性がある場合は適切な処置が必要。


$.when.applyを積極的につかってみよう!