Как дождаться последнего обещания в динамическом списке обещаний?

У меня есть функция 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()) но это не может быть необходимым в вашем случае.

Другие вопросы по тегам