Прогресс не просто так убрали из 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 сек, а вам нужно иметь возможность наблюдать за прогрессом - меняйте библиотеку.