$.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.