jQuery - отложенное ожидание массива запросов ajax для завершения даже сбоев

Как можно выполнить функцию после завершения ряда запросов ajax, независимо от того, были ли они успешными или ошибочными?

Я пытался использовать $.when.apply(this, array) для передачи массива отложенных объектов jqXHR. Однако, как говорят документы

В случае множественных отложенных вызовов, когда один из отложенных отклоняется, jQuery.when немедленно запускает failCallbacks для своего главного отложенного. Обратите внимание, что некоторые из отсроченных запросов могут все еще быть неразрешенными в этот момент.

Как можно использовать отложенные объекты jQuery, чтобы всегда ждать завершения всех вызовов ajax?

Может быть, мне следует создать свой собственный deferred, который будет обертывать все остальные deferred'ы? Если да, то я не совсем понимаю, как это настроить.


person berg    schedule 28.08.2012    source источник
comment
похоже, это решение, которое я искал. вам нужно обернуть каждый вызов ajax в отложенный, который разрешается с помощью полной функции. затем передайте весь массив в when().   -  person berg    schedule 28.08.2012


Ответы (1)


В духе того, как спецификация Promise, вероятно, будет развиваться в будущем с объектом PromiseInspection, вот дополнительная функция jQuery, которая сообщает вам, когда все обещания выполнены, независимо от того, выполнены они или отклонены:

(function() {    
    // pass either multiple promises as separate arguments or an array of promises
    $.settle = function(p1) {
        var args;
        if (Array.isArray(p1)) {
              args = p1;
        } else {
            args = Array.prototype.slice.call(arguments);
        }

        return $.when.apply($, args.map(function(p) {
            // make sure p is a promise (it could be just a value)
            p = wrapInPromise(p);
            // Make sure that the returned promise here is always resolved with a PromiseInspection object, never rejected
            return p.then(function(val) {
                return new PromiseInspection(true, val);
            }, function(reason) {
                // Convert rejected promise into resolved promise by returning a resolved promised
                // One could just return the promiseInspection object directly if jQuery was
                // Promise spec compliant, but jQuery 1.x and 2.x are not so we have to take this extra step
                return wrapInPromise(new PromiseInspection(false, reason));
            });
        })).then(function() {
              // return an array of results which is just more convenient to work with
              // than the separate arguments that $.when() would normally return
            return Array.prototype.slice.call(arguments);
        });
    }

    // utility functions and objects
    function isPromise(p) {
        return p && (typeof p === "object" || typeof p === "function") && typeof p.then === "function";
    }

    function wrapInPromise(p) {
        if (!isPromise(p)) {
            p = $.Deferred().resolve(p);
        }
        return p;
    }

    function PromiseInspection(fulfilled, val) {
        return {
            isFulfilled: function() {
                return fulfilled;
            }, isRejected: function() {
                return !fulfilled;
            }, isPending: function() {
                // PromiseInspection objects created here are never pending
                return false;
            }, value: function() {
                if (!fulfilled) {
                    throw new Error("Can't call .value() on a promise that is not fulfilled");
                }
                return val;
            }, reason: function() {
                if (fulfilled) {
                    throw new Error("Can't call .reason() on a promise that is fulfilled");
                }
                return val;
            }
        };
    }
})();

Затем вы можете использовать его следующим образом:

$.settle(promiseArray).then(function(inspectionArray) {
    inspectionArray.forEach(function(pi) {
        if (pi.isFulfilled()) {
            // pi.value() is the value of the fulfilled promise
        } else {
            // pi.reason() is the reason for the rejection
        }
    });
});

Имейте в виду, что $.settle() всегда будет выполняться (никогда не отклоняться), а выполненное значение представляет собой массив PromiseInspection объектов, и вы можете запросить каждый из них, чтобы увидеть, было ли оно выполнено или отклонено, а затем получить соответствующее значение или причину. См. демонстрацию ниже для примера использования:

Рабочая демонстрация: https://jsfiddle.net/jfriend00/y0gjs31r/

person jfriend00    schedule 05.03.2016
comment
Не избежит ли return $.when(p).then(...); необходимость всех этих типовых испытаний? - person Roamer-1888; 06.03.2016
comment
@ Roamer-1888 - Возможно, но я специально пытался избежать включения еще одного обещания, когда это не нужно. Итак, я тестирую случай, когда это уже обещание и, следовательно, его не нужно снова обертывать. Вероятно, я мог бы отключить проверку типов в вызове служебной функции isPromise(), и это выглядело бы чище. - person jfriend00; 06.03.2016
comment
Знаете ли вы, что $.when(p) возвращает p, если p является обещанием? - person Roamer-1888; 06.03.2016
comment
Вы правы, это особый случай, но здесь вы можете положиться, потому что внешний $.when() гарантирует, что внутренний $.when() увидит только выполненные обещания. - person Roamer-1888; 06.03.2016
comment
@ Roamer-1888 - я, конечно, мог бы заменить p = $.Deferred().resolve(p); на p = $.when(p); в качестве ярлыка кода, но я не понимаю, что еще вы предлагаете? Когда функция .map() работает, это обычно неурегулированные промисы в этот момент (если только промис не был передан $.settle(), что не является нормальным случаем проектирования). - person jfriend00; 06.03.2016
comment
Данг, т. map() выполняется первым! Я думал, что нашел эффективный способ избежать уродливого тестирования типов, но я думаю, что вы застряли с ним. - person Roamer-1888; 06.03.2016
comment
@ Roamer-1888 - Вы дали мне пару идей по очистке кода. Я добавлю те. - person jfriend00; 06.03.2016