Как дождаться последнего обещания в динамическом списке обещаний?
У меня есть функция F, которая запускает асинхронный процесс X. Функция возвращает обещание, которое разрешается, когда X заканчивается (что я узнаю с помощью обещания, возвращенного X).
Во время работы (wlog) первого экземпляра X, X1, может быть больше вызовов F. Каждый из них вызовет новый экземпляр X, например, X2, X3 и т. Д.
Теперь вот трудность: когда X2 создается, в зависимости от состояния X1, X1 должен завершиться или прерваться. X2 должен начать работать только тогда, когда X1 больше не активен. В любом случае, неразрешенные обещания, возвращенные из всех предыдущих вызовов F, должны быть разрешены только после того, как X2 завершит, а также, или в любом последующем случае X, если F будет вызван снова во время работы X2.
Пока первый вызов F вызывает $q.defer()
создать отложенное, чье обещание возвращается всеми вызовами F до тех пор, пока не завершится последний X. (Затем отложенное значение должно быть разрешено, и поле, содержащее его, должно быть сброшено в ноль, ожидая следующего кластера вызовов F.)
Теперь моя проблема ждет, пока все экземпляры X закончатся. Я знаю, что я мог бы использовать $q.all
если бы у меня был полный список экземпляров X заранее, но, поскольку я должен был учесть более поздние обращения к F, это не решение здесь. В идеале, я должен, вероятно, then
-прицепить что-то к обещанию, возвращенному X, чтобы разрешить отложенное, и "расцепить" эту функцию, как только я приковую ее к более позднему экземпляру X.
Я представляю, что-то вроде этого:
var currentDeferred = null;
function F() {
if (!currentDeferred) {
currentDeferred = $q.defer();
}
// if previous X then "unchain" its promise handler
X().then(function () {
var curDef = currentDeferred;
currentDeferred = null;
curDef.resolve();
});
return currentDeferred.promise;
}
Однако я не знаю, как выполнить это "освобождение", если это даже правильное решение.
Как мне это сделать? Я пропускаю какой-то общий шаблон или даже встроенную функцию обещаний, или я вообще не на том пути?
Чтобы добавить небольшой контекст: F вызывается для загрузки данных (асинхронно) и обновления некоторого визуального вывода. F возвращает обещание, которое должно быть разрешено только после того, как визуальный вывод снова обновится до стабильного состояния (т. Е. Больше не ожидается обновление).
1 ответ
F вызывается для загрузки данных (асинхронно) и обновления некоторого визуального вывода. F возвращает обещание, которое должно быть разрешено только после того, как визуальный вывод снова обновится до стабильного состояния (т. Е. Больше не ожидается обновление).
Поскольку все звонящие F
получит обещание, которое им нужно использовать, но вы хотите обновить интерфейс только после завершения всех суммированных вызовов. Самое простое - это разрешить каждое обещание (или отклонить) со значением, указывающим вызывающей стороне не обновлять интерфейс, если есть ожидание другого вызова "получить больше данных"; таким образом, только пользователь, чье обещание разрешается последним, будет обновлять пользовательский интерфейс. Вы можете сделать это, отслеживая ожидающие звонки:
let accumulator = [];
let outstanding = 0;
function F(val) {
++outstanding;
return getData(val)
.then(data => {
accumulator.push(data);
return --outstanding == 0 ? accumulator.slice() : null;
})
.catch(error => {
--outstanding;
throw error;
});
}
// Fake data load
function getData(val) {
return new Promise(resolve => {
setTimeout(resolve, Math.random() * 500, "data for " + val);
});
}
let accumulator = [];
let outstanding = 0;
function F(val) {
++outstanding;
return getData(val)
.then(data => {
accumulator.push(data);
return --outstanding == 0 ? accumulator.slice() : null;
})
.catch(error => {
--outstanding;
throw error;
});
}
// Resolution and rejection handlers for our test calls below
const resolved = data => {
console.log("chain done:", data ? ("update: " + data.join(", ")) : "don't update");
};
const rejected = error => { // This never gets called, we don't reject
console.error(error);
};
// A single call:
F("a").then(resolved).catch(rejected);
setTimeout(() => {
// One subsequent call
console.log("----");
F("b1").then(resolved).catch(rejected);
F("b2").then(resolved).catch(rejected);
}, 600);
setTimeout(() => {
// Two subsequent calls
console.log("----");
F("c1").then(resolved).catch(rejected);
F("c2").then(resolved).catch(rejected);
F("c3").then(resolved).catch(rejected);
}, 1200);
.as-console-wrapper {
max-height: 100% !important;
}
(Это с родными обещаниями; корректировать по мере необходимости для $q
.)
Для меня "не обновлять" отличается от "не удалось", поэтому я использовал значение флага (null
), а не отказ, чтобы сигнализировать об этом. Но, конечно, вы также можете использовать отклонение со значением флага, это ваше дело. (И это было бы полезным, если бы в вашей условной логике была помещена условная логика ["Это реальная ошибка или просто" не обновлять "?]") catch
обработчик, а не ваш then
[это реальные данные или нет?]... Хм, я мог бы пойти другим путем, теперь я думаю об этом. Но это тривиальное изменение.)
очевидно accumulator
в приведенном выше примере это просто грубый заполнитель для ваших реальных структур данных (и он не пытается сохранить полученные данные в том порядке, в котором они были запрошены).
У меня есть обещание решить с копией данных в выше (accumulator.slice()
) но это не может быть необходимым в вашем случае.