JQuery.when - Обратный вызов, когда ВСЕ ОТЧЕТЫ больше не "не разрешены" (разрешены или отклонены)?

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

Метод будет либо

  • разрешить своего хозяина Отложенное, как только ВСЕ отложенные решения будут разрешены, или
  • отклонить своего хозяина Отложить, как только ОДИН из Отложенных будет отклонен.

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

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

    // foo & bar are jqXHR objects for the requests

});

В многократном случае Отсрочки, когда одна из Отложенных отклонена, jQuery.when НЕПРАВИЛЬНО ПОЛУЧАЕТ обратные вызовы сбоя для своего хозяина Отсрочка, даже если некоторые из Отсрочек могут все еще быть неразрешенными в этой точке:

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

    // req is the jqXHR object for one of the failed requests

});

Мне нужно запустить обратный вызов, когда все Отложенные переданы в jQuery. Когда они больше не "неразрешены" (т.е. все либо "разрешены", либо "отклонены" ). Я мог бы отправлять объекты JSON с 200 кодами OK (вместо отправки JSON с кодами статуса ошибки 404 Not Found) и определить успех/ошибку в методе done(), но я бы предпочел сохранить мой API RESTful. Как я могу это сделать?

Ответ 1

Я думаю, что самый простой способ сделать это - сохранить вторичный объект 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 добавляет к его promises для методов AJAX, который вызывается как для разрешенных, так и для отклоненных promises.

NB: d1.resolve работает как обратный вызов сам по себе, его не нужно обертывать в блок function() { ... }.

Ответ 2

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

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

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

"Когда" преобразует массив $offferreds в список аргументов для "когда", и, поскольку этот набор отсрочек гарантированно разрешен (благодаря всегда), теперь можно определить "сделанный", который будет вызывается, как только все асинхронные вызовы завершены независимо от того, выполняются ли они с ошибкой/.

Ответ 3

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

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

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

Ответ 4

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

Код плагина:

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/

Ответ 5

Здесь плагин 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% точно такое же, за исключением семантики.

Ответ 6

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

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

Ответ 7

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

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.

Ответ 8

Я нашел решение, в котором у меня есть 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);                  
            }
        );