web-dev-qa-db-ja.com

jQuery.when-ALL Deferredが「未解決」ではなくなった(解決または拒否された)場合のコールバック?

複数のDeferredオブジェクトが jQuery.when に渡されると、メソッドは、渡されたすべてのDeferredの集約状態を追跡する新しい「マスター」DeferredオブジェクトからPromiseを返します。

メソッドは

  1. すべての遅延オブジェクトが解決されるとすぐにマスターの遅延オブジェクトを解決する、または
  2. deferredの1つが拒​​否されるとすぐに、マスターDeferredを拒否します。

マスター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にしておくことをお勧めします。どうすればこれを達成できますか?

38
Alan Spacer

これを行う最も簡単な方法は、各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() { ... }ブロックでラップする必要はありません。

45
Alnitak

@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」を定義できるようになりました。非同期呼び出しは、これらが成功したか失敗したかに関係なく完了します。

11
DazWilkin

最近、役立つプラグインを作成しました。 $.whenAll

この拡張機能は、すべての成功と失敗を進行状況イベントとして扱います。すべての約束が完了した後、エラーがなければ、グローバル約束が解決されます。それ以外の場合、グローバルな約束は拒否されます。

$ .whenAll- https://Gist.github.com/4341799tests

使用例:

$.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
      }
    }
  )
;
9
fearphage

私の実装:

プラグインコード:

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/

8
leojh

これが、セマンティクスを使用するように$.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()とともに使用できるようになります。それ以外のすべては、セマンティクスを除いてまったく同じです。

3
hippietrail

ときに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);                  
            }
        );
0
Tom Schreck

サーバーからリソースをフェッチするだけではなく、たとえばユーザーインタラクションによってトリガーされたイベントや非同期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"
    });
0
baohouse

@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関数を使用しています。

0
lagivan