$.Deferred: как определить, когда выполнено каждое обещание

У меня есть ряд асинхронных задач, которые необходимо выполнить, поэтому я использую promises.

Мне нужно определить, когда был выполнен каждый из promises (оба разрешены и отклонены). Я не должен продолжать выполнение до этой точки.

Я использовал что-то вроде этого:

$.when(promise1, promise2, ...).always();

Но этот код неверен, потому что метод when имеет ленивую оценку, и он возвращается, как только один из promises терпит неудачу. Таким образом, обратный вызов always также запускается, как только один из promises выходит из строя.

Я думал о кодировании обходного пути, но этот пример использования настолько распространен, что, возможно, кто-то уже это сделал, или, возможно, даже способ сделать это, используя только jQuery (если нет, было бы неплохо добавить Promise.whenNonLazy или Promise.when(promise1, promise2, ..., false) в будущем.

Возможно ли это?

Ответ 1

Более сложные библиотеки обещаний имеют allSettled() функцию вроде Q или Promise.settle, как Bluebird.

В jQuery вы также можете реализовать такую ​​функцию и расширить пространство имен $ с ней, но это будет необходимо, только если вам это нужно часто и оптимизировано по производительности.

Более простым решением было бы создать новое обещание для каждого из тех, кого вы ожидаете, и выполнять их, даже если базовый отклоняется. Тогда вы можете использовать $.when() для них без проблем. Короче говоря:

// using Underscore .invoke() method:
$.when.apply(null, _.invoke(promises, "then", null, $.when)).done(…)

Более стабильный:

$.when.apply($, $.map(promises, function(p) {
    return p.then(null, function() {
        return $.Deferred().resolveWith(this, arguments);
    });
})).then(…);

Вы можете немного изменить обратные вызовы then, чтобы отличить отработанные и отклоненные результаты в окончательном done.

Ответ 2

Что интересное свойство always - я не ожидал такого поведения.

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

//set up master deferred, to observe the states of the sub-deferreds
var master_dfd = new $.Deferred;
master_dfd.done(function() { alert('done'); });

//set up sub-deferreds
var dfds = [new $.Deferred, new $.Deferred, new $.Deferred];
var cb = function() {
    if (dfds.filter(function(dfd) {
        return /resolved|rejected/.test(dfd.state());
    }).length == dfds.length)
        master_dfd.resolve();
};
dfds.forEach(function(dfd) { dfd.always(cb); });

//resolve or reject sub-deferreds. Master deferred resolves only once
//all are resolved or rejected
dfds[0].resolve();
dfds[1].reject();
dfds[2].resolve();

Fiddle: http://jsfiddle.net/Wtxfy/3/

Ответ 3

Смити,

Сначала предположим, что ваш promises находится в массиве.

var promises = [....];

То, что вы хотите, - это .when(), примененное к некоторому преобразованию этих promises, так что любое отклоненное обещание преобразуется в разрешенное, будучи прозрачным для promises, которые уже разрешены.

Требуемая операция может быть написана очень кратко следующим образом:

$.when.apply(null, $.map(promises, resolvize)).done(...);
//or, if further filtering by .then() is required ...
$.when.apply(null, $.map(promises, resolvize)).then(...);

где resolvize - механизм преобразования.

Так что должно выглядеть resolvize(), похоже? Позвольте использовать характеристики .then(), чтобы сделать различие между разрешенным и отвергнутым обещанием и соответствующим образом отреагировать.

function resolvize(promise) {
    //Note: null allows a resolved promise to pass straight through unmolested;
    return promise.then(null, function() {
        return $.Deferred().resolve.apply(null, arguments).promise();
    });
}

непроверенных

С помощью resolvize в некоторой внешней области он может быть доступен для использования в выражении $.when.apply($.map(promises, resolvize)), где бы он ни понадобился. Это, скорее всего, адекватно, не вдаваясь в расширение jQuery с помощью нового метода.

Независимо от того, как достигается преобразование, вы получаете потенциальную проблему; а именно знание каждого аргумента обратного вызова .done(), было ли его соответствующее обещание первоначально разрешено или отклонено. Это цена, которую вы платите за конвертацию отказа в разрешение. Тем не менее, вы можете обнаружить исходное состояние из параметра (ов), с которым были разрешены/отклонены исходный promises.