はじめまして。「ぱいな」と申します。
私は自称ネットワークエンジニアなのですが,最近 Web まわりもちょっと勉強していて,jQuery を使ってます。この投稿は jQuery.Deferred についてです。タイトルに "Tips" と書きましたが,本音は「これもっと良い方法あるんじゃね?」というところなので,もし良い方法があったらコメントくださいまし。
Deferred 関数の逐次処理
さて,非同期処理の強い味方,jQuery.Deferred ですが,可変個の Deferred 関数を扱うケースがあると思います。$.when.apply()
を使って配列として渡してあげれば,一気に処理してくれますね。
// jQuery.Deferred を使った何か非同期処理をする関数
function deferredFunc(mesg) {
var dfd = $.Deferred();
setTimeout(function () { // 0~5秒のランダムな時間の後に引数をログ出力する
console.log(mesg);
dfd.resolve();
}, Math.random() * 5000);
return dfd.promise();
}
// これを可変個とする
var messages = ["poi", "poe", "pui"];
// 可変個の配列を map して要素を deferredFunc に渡す
var deferreds = messages.map(function (mesg) {
return deferredFunc(mesg);
});
// 全ての deferredFunc が resolve したら "Done!" とログ出力する
$.when.apply($, deferreds).done(function () {
console.log("Done!");
});
しかし,Deferred 関数を逐次処理したいケースも,希かもしれませんがありうるでしょう。(「jQuery.Deferred 使わないで普通に書けば?」というツッコミもありそうですが,Deferred 関数をいじれない時などです。)
Deferred 関数の個数が決まっていれば then()
をチェインしていけば良いです。今回の例では無名関数で包む必要があるのでちょっと面倒ですが,こんな感じに。
deferredFunc("one").then(function () {
return deferredFunc("two");
}).then(function () {
return deferredFunc("three");
});
"one", "two", "three" が順番にログ出力されます。
では Deferred 関数の個数,というか Deferred 関数で処理したい配列等の個数が可変個だったら。これしか思いつかなかったのですが reduce()
でチェインしてみました。
var messages = ["one", "two", "three"];
var deferreds = messages.map(function (mesg) { // ここではまだ実行したくないので,無名関数でくるむ
return function () {
return deferredFunc(mesg);
};
});
deferreds.reduce(function (prev, curr) {
return prev.then(function () {
return curr();
});
}, $.Deferred().resolve());
無名関数の嵐! でも,これで "one", "two", "three" が順番にログ出力されます。
もっと良い方法を募集中ー。
可変個の $.when.apply().done()
の返値を処理する
可変個の Deferred 関数の返値(正確には resolve()
の引数)を処理したいときもちょっと悩みました。
例えば,Deferred 関数を次のように変えてみます。
function deferredFunc(mesg) {
var dfd = $.Deferred();
setTimeout(function () { // 0~5秒のランダムな時間の後に引数を resolve() する
dfd.resolve(mesg);
}, Math.random() * 5000);
return dfd.promise();
}
前章の deferredFunc()
と異なる点は,引数を関数内で console.log()
するのではなく,resolve()
する点です。可変個の deferredFunc()
を $.when.apply().done()
にかけたとき,返値を処理するにはどうするか悩みました。これも良い方法を募集中なのですが,こんな感じで一応解決。
var messages = ["one", "two", "three"];
var deferreds = messages.map(function (mesg) {
return deferredFunc(mesg);
});
$.when.apply($, deferreds).done(function () {
for(var i = 0; i < arguments.length; i++) { // arguments は forEach() 使えない
console.log(arguments[i]);
}
console.log("Done");
});
JavaScript において,関数の引数は仮引数だけではなく arguments
でも受けられます。こうすれば,全ての deferredFunc()
が resolve()
した後,"one", "two", "three" が一度にログ出力されます。