複数のDeferredオブジェクトが jQuery.when に渡されると、メソッドは、渡されたすべてのDeferredの集約状態を追跡する新しい「マスター」DeferredオブジェクトからPromiseを返します。
メソッドは
マスターDeferredが解決される場合(つまり、すべてのDeferredが解決される)、jQuery.whenに渡されたすべてのDeferredの解決された値が渡されます。たとえば、DeferredがjQuery.ajax()リクエストの場合、引数はリクエストのjqXHRオブジェクトであり、引数リストに指定された順序になります。
$.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) {
// foo & bar are jqXHR objects for the requests
});
Deferredの1つが拒否される複数のDeferredの場合、jQuery.whenは、その時点でまだいくつかのDeferredが未解決である場合でも、マスターDeferredの失敗コールバックを即時に起動します。
$.when( $.getJSON('foo'), $.getJSON('bar') ).fail(function(req) {
// req is the jqXHR object for one of the failed requests
});
JQuery.whenに渡されたすべてのDeferredが「未解決」ではなくなった場合(つまり、すべてが「解決済み」または「拒否済み」の場合)、コールバックを起動する必要があります。 200 OKコードを含むJSONオブジェクトを送信し(404 Not Foundエラーステータスコードを含むJSONを送信する代わりに)、done()メソッドで成功/エラーを判断することはできますが、APIをRESTfulにしておくことをお勧めします。どうすればこれを達成できますか?
これを行う最も簡単な方法は、各AJAXリクエストに対してセカンダリDeferred
オブジェクトを保持し、that 1が常に解決されるようにすることです) :
_var d1 = $.Deferred();
var d2 = $.Deferred();
var j1 = $.getJSON(...).complete(d1.resolve);
var j2 = $.getJSON(...).complete(d2.resolve);
$.when(j1, j2).done( only fires if j1 AND j2 are resolved );
$.when(d1, d2).done(function() {
// will fire when j1 AND j2 are both resolved OR rejected
// check j1.isResolved() and j2.isResolved() to find which failed
});
_
これは、追加のAJAX .complete()
メソッドを使用しており、jQueryはAJAXメソッドのプロミスに追加します)と拒否された約束。
注意:_d1.resolve
_は、それ自体がコールバックとして機能します。function() { ... }
ブロックでラップする必要はありません。
@Alnitakの回答は巧妙で、作成したハックを消去するのに役立ちました。基本的な結果に関係なく、「いつ」を使用して複数のリクエストをバッチ処理し、「完了」を使用できるようにするために、人為的に約束を解決しました。成功/失敗に関係なく続行します。
私はアルニタックの答えに「答える」ために、任意の数の根本的な約束をサポートする彼の提案の別の使用法を提供することを期待しています。
var asyncFunc, entity, entities, $deferred, $deferreds;
// ...
foreach (entity in entities) {
$deferred = $.Deferred();
$deferreds.Push($deferred);
asyncFunc(entity).done(...).fail(...).always($deferred.resolve);
}
// ...
$.when.apply($, $deferreds).done(...)
これは疑似JavaScriptですが、アプローチを伝える必要があります。任意のサイズのエンティティセットについて、各エンティティの遅延($ deferred)を作成し、それを配列($ deferreds)にプッシュし、非同期呼び出しを行い、必要に応じてdone/failを追加しますが、常にこれを解決する「常に」を含めますエンティティの$ deferred。 NB 「常に」は、その呼び出しではなく、据え置きの解決機能を受け取ります。
'when'は$ deferreds配列を 'when'の引数リストに変換します。この遅延セットは解決されることが保証されているため(alwaysのおかげで)、一度だけ呼び出される「done」を定義できるようになりました。非同期呼び出しは、これらが成功したか失敗したかに関係なく完了します。
最近、役立つプラグインを作成しました。 $.whenAll
。
この拡張機能は、すべての成功と失敗を進行状況イベントとして扱います。すべての約束が完了した後、エラーがなければ、グローバル約束が解決されます。それ以外の場合、グローバルな約束は拒否されます。
$ .whenAll- https://Gist.github.com/4341799 ( tests )
使用例:
$.whenAll($.getJSON('foo'), $.getJSON('bar'))
.then(
doneCallback
,failcallback
// progress callback
// the only problem is $.ajax.done/fail states call their callbacks
// with params in different locations (except for state)
,function(data, state, jqXhr) {
if (state == 'success') {
// do happy stuff
}
else { // error (fail)
// `data` is actually the jqXhr object for failed requests
// `jqXhr` is the text of the error "Not Found" in this example
}
}
)
;
私の実装:
プラグインコード:
jQuery.whenAll = function (deferreds) {
var lastResolved = 0;
var wrappedDeferreds = [];
for (var i = 0; i < deferreds.length; i++) {
wrappedDeferreds.Push(jQuery.Deferred());
deferreds[i].always(function() {
wrappedDeferreds[lastResolved++].resolve(arguments);
});
}
return jQuery.when.apply(jQuery, wrappedDeferreds).promise();
};
それを使用するには:
jQuery.whenAll([jQuery.get('/your-resource'), jQuery.get('/your-resource')])
.done(
function(result1, result2) {
console.log(result1[1]);
console.log(result2[1]);
});
フィドルをチェックしてください:http://jsfiddle.net/LeoJH/VMQ3F/
これが、セマンティクスを使用するように$.when()
の実際のコアコードを変更して作成したjQueryプラグインです。よりよい名前が必要な場合は、$.myWhen()
と呼ばれます。
_(function($) {
$.myWhen = function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
responseValues = Array.prototype.slice.call( arguments ),
length = responseValues.length,
// the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// the master Deferred. If responseValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for all resolve, reject and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? Array.prototype.slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, responseContexts;
// add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
responseContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( responseValues[ i ] && jQuery.isFunction( responseValues[ i ].promise ) ) {
responseValues[ i ].promise()
.always( updateFunc( i, responseContexts, responseValues ) )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( responseContexts, responseValues );
}
return deferred.promise();
};
})(jQuery);
_
JQueryをロードした直後にこのコードを配置すると、$.myWhen()
関数が$.when()
とともに使用できるようになります。それ以外のすべては、セマンティクスを除いてまったく同じです。
ときに2つのリクエストがあり、リクエストの1つが失敗した場合でも個々の成功にアクセスできるソリューションを見つけました。
$.when
(
$.getJSON(...).then(function (results)
{
console.log('SUCCESS REQUEST 1 BY ITSELF', results);
}),
$.getJSON(...).then(function (results)
{
console.log('SUCCESS REQUEST 2 BY ITSELF', results);
})
).then
(
function (results1, results2)
{
console.log('BOTH REQUESTS SUCCESSFUL...');
console.log('results1', results1);
console.log('results2', results2);
},
function (error1, error2)
{
console.log('AT LEAST 1 REQUEST FAILED...');
console.log('error1', error1);
console.log('error2', error2);
}
);
サーバーからリソースをフェッチするだけではなく、たとえばユーザーインタラクションによってトリガーされたイベントや非同期jQuery UI呼び出し(slideUp()やslideDown()など)を含めることができる、より一般的なユースケース向けのLeo Hernandezのソリューションの改善。拡張された使用例については https://jsfiddle.net/1trucdn3/ を参照してください。
$.whenAll = function (deferreds) {
var lastResolved = 0;
var wrappedDeferreds = [];
for (var i = 0; i < deferreds.length; i++) {
wrappedDeferreds.Push($.Deferred());
if (deferreds[i] && deferreds[i].always) {
deferreds[i].always(wrappedDeferreds[lastResolved++].resolve);
} else {
wrappedDeferreds[lastResolved++].resolve(deferreds[i]);
}
}
return $.when.apply($, wrappedDeferreds).promise();
};
この改善により、非遅延値を配列引数に渡すことができます。これは$ .when()でできることでした。また、ステータスに関係なく結果を取得したい場合に備えて、コールバック関数で取得した出力をクリーンアップして、元の$ .when()メソッドの動作に合わせてインライン化しました。 Leoのソリューションは結果として据え置きオブジェクト全体を渡すため、必要な情報を見つけるためにそれを掘り下げる必要があります。
$.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")])
.done(function (result1, result2, result3) {
// result1 -> 1
// result2 -> "Good"
// result3 -> "Bad"
});
@Alnitakと@DazWilkinの回答は素晴らしいです!しかし、私は個人的には関数型スタイルを好むので、ここに任意の数のpromiseの関数型バージョンを示します。
var entities;
// ...
var deferreds = entities.map(function() {
var deferred = $.Deferred();
asyncFunc(this).done(...).fail(...).always(deferred.resolve);
return deferred;
}
// ...
$.when.apply($, deferreds).done(...)
@DazWilkinの回答と比較して、map
ではなくforeach
関数を使用しています。