jQuery.when - обратный вызов для случаев, когда ВСЕ отложенные больше не являются «неразрешенными» (разрешенными или отклоненными)? - PullRequest
36 голосов
/ 29 апреля 2011

Когда несколько объектов Deferred передаются в jQuery.when , метод возвращает Promise от нового «главного» объекта Deferred, который отслеживает совокупное состояние всех объектов Deferred, которые ему были переданы.

Метод либо

  1. разрешит своего мастера Deferred, как только ВСЕ отложат разрешение, либо
  2. отклонит своего мастера Deferred, как только ОДИН из Deferreds будет отклонен.

Если мастер Deferred разрешен (т. Е. ALL Deferreds разрешается), ему передаются разрешенные значения всех Deferred, которые были переданы в jQuery.when.Например, когда Deferreds являются запросами jQuery.ajax (), аргументы будут объектами jqXHR для запросов в том порядке, в котором они были указаны в списке аргументов:

$.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) {

    // foo & bar are jqXHR objects for the requests

});

В случае с несколькими Deferredsгде один из Отложенных отклонен, jQuery.when НЕМЕДЛЕННО запускает обратные вызовы сбоя для своего основного Отложенного, даже если некоторые из Отложенных могут все еще быть неразрешенными в тот момент:когда все Отложенные, переданные в jQuery.when, больше не являются «неразрешенными» (т. е. все «разрешены» или «отклонены»).Я мог бы отправлять объекты JSON с 200 кодами OK (вместо этого отправляя JSON с кодами ошибок 404 Not Found) и определять успех / ошибку в методе done (), но я бы предпочел сохранить мой API RESTful.Как мне это сделать?

Ответы [ 8 ]

44 голосов
/ 29 апреля 2011

Я думаю, что самый простой способ сделать это - сохранить вспомогательный объект Deferred для каждого запроса AJAX и убедиться, что этот один всегда разрешается:

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() { ... }.

11 голосов
/ 03 августа 2012

@ Ответ Альнитака умный и помог мне стереть созданный мною хак, в котором я несколько искусственно разрешал обещание - независимо от базового результата - чтобы я мог использовать «когда» для пакетирования нескольких запросов и использования «готово», чтобы продолжить независимо от их успеха / неудачи.

Я «отвечаю» на ответ Альнитака в надежде на другое использование его предложения, которое поддерживает произвольное количество основных обещаний.

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 ($ deferred) для каждой сущности и поместите его в массив ($ deferreds), сделайте асинхронный вызов, добавьте done / fail по желанию, но всегда включайте «всегда», которое разрешает это $ отсрочки. NB 'всегда' получает функцию разрешения отложенного, а не ее вызов.

Когда 'преобразует массив $ deferreds в список аргументов для' когда ', и, поскольку этот набор отложенных гарантированно разрешает (благодаря всегда), теперь можно определить' готово ', которое будет Вызывается, когда все асинхронные вызовы завершены независимо от того, успешны они или нет.

9 голосов
/ 30 января 2013

Я недавно сделал плагин, который может помочь.Я называю это $.whenAll.

Это расширение обрабатывает все успехи и неудачи как события прогресса.После выполнения всех обещаний глобальное обещание разрешается, если ошибок не было.Иначе глобальное обещание отклоняется.

$. WhenAll - https://gist.github.com/4341799 ( тесты )

Пример использования:

$.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
      }
    }
  )
;
6 голосов
/ 26 февраля 2014

Моя реализация:

Код плагина:

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/

3 голосов
/ 22 августа 2012

Вот плагин jQuery, который я сделал, изменив фактический код ядра для $.when(), чтобы использовать вашу семантику.Если вам не хватает лучшего имени, оно называется $.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().Все остальное на 100% точно так же, за исключением семантики.

0 голосов
/ 26 января 2016

Я нашел решение, в котором у меня есть 2 запроса, когда я могу получить доступ к отдельным успехам, даже если один из запросов не выполнен:

        $.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 голосов
/ 16 июня 2015

@ Ответы Alnitak и @DazWilkin великолепны! Но я лично предпочитаю функциональный стиль, поэтому вот функциональная версия для произвольного числа обещаний:

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 голосов
/ 21 мая 2015

Улучшение решения Лео Эрнандеса для более общих случаев использования, которые не включают просто выборку ресурсов с сервера, которая, например, может включать события, инициируемые пользовательскими взаимодействиями, или асинхронные вызовы jQuery UI (например, slideUp () и slideDown ()).См. 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 (), на случай, если вы просто захотите получить результат независимо от статуса.В результате решение Лео передаст весь отложенный объект, который вам нужно будет найти, чтобы найти нужную информацию.

$.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")])
    .done(function (result1, result2, result3) {
        // result1 -> 1
        // result2 -> "Good"
        // result3 -> "Bad"
    });
...