Как отслеживать прогресс Promise?

Добрый вечер!
Есть довольно долгий промис (20+ минут), который хотелось бы отслеживать. Как я понимаю сейчас из A+ выпилили #progress. Как можно отслеживать прогресс выполнения промиса, желательно в чейне?

Думаю передавать ее в #then тоже не лучший вариант.

P.S. Библиотеку Q не предлагайте, она медленная и не соответствует половине стандарта.
  • Вопрос задан
  • 1233 просмотра
Пригласить эксперта
Ответы на вопрос 1
Прогресс не просто так убрали из Promise, в большинстве случаев это антипаттерн.
Нужно понимать что Promise A+ спроектированы так, чтобы инкапсулировать внутри себя всю связанную с операцией логику.
Именно поэтому я не советую вам придумывать код, который требует методов Promise.progress, Promise.cancel или свойств Promise.isFullfilled, Promise.isRejected.

Т.е. вы все еще можете пользоваться этими свойствами, но только не в контексте A+.
Берите Q или любую легковесную замену, и реализуйте все что хотите через defer.
Примером того, что вы получите в итоге может являться ответ @onqu

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

Отменяйте промис только внутри него. Сообщайте об изменении состояния только с помощью then. Если вам нужно отслеживать прогресс загрузки чего либо, делайте это через события или каллбеки:

function doSomethingAsync(timeout, cb) {
  var ee = new EventEmitter();
  var state = {
    progress: 0
  };

  (function loop() {
    if (state.progress === 22) return cb(null, state);
    if (state.progress === 'canceled') return cb(new Error('Action canceled'));
    if (state.progress * 1000 > timeout) return cb(new Error('Action timed out'));

    ee.emit('progress', state);
    setTimeout(loop, 1000);
  })();

  return ee;
}


function a20SecAction(actions = {}) {
  var maxActionTime = 20000;

  return new Promise((resolve, reject) => {
    var actionWithProgress = doSomethingAsync(maxActionTime, (err, result) =>
      (err ? reject : resolve)(err || result)
    );

    actionWithProgress.on('progress', actions.progress);
  });
}

a20SecAction({
  progress: (state) => console.log('state:', state.progress)
}).then(
  (res) => console.log('state: ready'),
  (err) => console.log('state:', err)
);


Итак, если у вас есть действие, которое в любом случае должно завершиться, но вам нужно отслеживать прогресс (например загрузки файла), то не пытайтесь решить это на уровне промиса (A+) он вам не подойдет.
В моем примере три этапа:
1) Некоторая простейшая асинхронная функция оформленная в nodejs стиле, т.е. она принимает набор параметров и каллбек вида function (err, result) {}
Это ваш минимальный строительный блок
2) Функция обертка - более высокий уровень, на котором мы начинаем работать с промисом. Как параметры функция принимает обработчики дополнительных событий нашей операции. В данном случае у операции одно дополнительное событие progress, и два основных - fullfilled, rejected.
3) Задаем обработчики для всех событий, основные обрабатываем в then, дополнительные через заданные параметры.
В данном случае я оставил возможность влиять на процесс выполнения извне, внутри обработчика progress. Но лишь для демонстрации того, что это в принципе возможно. На практике лучше не допускать ситуации, когда изменение состояния асинхронной операции происходит извне:
a20SecAction({
  progress: (state) => {
    console.log('state:', state.progress);
    if (state.progress === 7) state.progress = 'canceled';
  }
}).then(
  (res) => console.log('state: ready'),
  (err) => console.log('state:', err)
);


Ну и в заключении, если у вас уже есть внешняя библиотека, которая предоставляет вам обычный промис для действия, которое может длиться 20 сек, а вам нужно иметь возможность наблюдать за прогрессом - меняйте библиотеку.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы