web-dev-qa-db-fra.com

ES6 Promise.all progress

J'ai plusieurs promesses que je dois tenir avant d'aller plus loin.

Promise.all(promises).then((results) => {
  // going further
}); 

Existe-t-il un moyen de suivre la progression du Promise.all promettre?

D'après le doc, il apparaît que ce n'est pas possible . Et cette question n'y répond pas non plus.

Alors:

  • N'êtes-vous pas d'accord que cela serait utile? Ne devrions-nous pas interroger cette fonctionnalité?
  • Comment peut-on l'implémenter manuellement pour l'instant?
18
Augustin Riedinger

J'ai créé une petite fonction d'aide que vous pouvez réutiliser.

En gros, passez vos promesses comme d'habitude et rappelez-vous pour faire ce que vous voulez avec la progression.

function allProgress(proms, progress_cb) {
  let d = 0;
  progress_cb(0);
  for (const p of proms) {
    p.then(()=> {    
      d ++;
      progress_cb( (d * 100) / proms.length );
    });
  }
  return Promise.all(proms);
}

function test(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
       console.log(`Waited ${ms}`);
       resolve();
     }, ms);
  });
}


allProgress([test(1000), test(3000), test(2000), test(3500)],
  (p) => {
     console.log(`% Done = ${p.toFixed(2)}`);
});
31
Keith

Vous pouvez ajouter un .then () à chaque promesse de compter les personnes terminées. quelque chose comme :

var count = 0;

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 5000, 'boo');
}); 
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 7000, 'yoo');
}); 
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, 'foo');
}); 

var promiseArray = [
  p1.then(function(val) {
    progress(++count); 
    return val 
  }), 
  p2.then(function(val) {
    progress(++count); 
    return val 
  }), 
  p3.then(function(val) {
    progress(++count); 
    return val 
  })
]

function progress(count) {
  console.log(count / promiseArray.length);
}

Promise.all(promiseArray).then(values => { 
  console.log(values);
});
5
naortor

Cela présente quelques avantages par rapport à réponse de Keith :

  • La fonction de rappel onprogress() n'est jamais invoquée de manière synchrone. Cela garantit que le rappel peut dépendre du code qui est exécuté de manière synchrone après l'appel à Promise.progress(...).
  • La chaîne de promesses propage les erreurs lancées dans les événements de progression à l'appelant plutôt que d'autoriser les rejets de promesses non capturés. Cela garantit qu'avec une gestion robuste des erreurs, l'appelant est en mesure d'empêcher l'application d'entrer dans un état inconnu ou de se bloquer.
  • Le rappel reçoit un ProgressEvent au lieu d'un pourcentage. Cela facilite la manipulation de 0 / 0 progresse les événements en évitant le quotient NaN.
Promise.progress = async function progress (iterable, onprogress) {
  // consume iterable synchronously and convert to array of promises
  const promises = Array.from(iterable).map(this.resolve, this);
  let resolved = 0;

  // helper function for emitting progress events
  const progress = increment => this.resolve(
    onprogress(
      new ProgressEvent('progress', {
        total: promises.length,
        loaded: resolved += increment
      })
    )
  );

  // lift all progress events off the stack
  await this.resolve();
  // emit 0 progress event
  await progress(0);

  // emit a progress event each time a promise resolves
  return this.all(
    promises.map(
      promise => promise.finally(
        () => progress(1)
      ) 
    })
  );
};

Notez que ProgressEvent a un support limité . Si cette couverture ne répond pas à vos besoins, vous pouvez facilement remplir ceci:

class ProgressEvent extends Event {
  constructor (type, { loaded = 0, total = 0, lengthComputable = (total > 0) } = {}) {
    super(type);
    this.lengthComputable = lengthComputable;
    this.loaded = loaded;
    this.total = total;
  }
}
3
Patrick Roberts

Voici mon point de vue à ce sujet. Vous créez un wrapper pour progressCallback et indiquez le nombre de threads dont vous disposez. Ensuite, pour chaque thread, vous créez un rappel distinct de ce wrapper avec l'index de thread. Threads chaque rapport via leur propre rappel comme auparavant, mais leurs valeurs de progression individuelles sont ensuite fusionnées et signalées via le rappel encapsulé.

function createMultiThreadProgressWrapper(threads, progressCallback) {
  var threadProgress = Array(threads);

  var sendTotalProgress = function() {
    var total = 0;

    for (var v of threadProgress) {
      total = total + (v || 0);
    }

    progressCallback(total / threads);
  };

  return {
    getCallback: function(thread) {
      var cb = function(progress) {
        threadProgress[thread] = progress;
        sendTotalProgress();
      };

      return cb;
    }
  };
}

// --------------------------------------------------------
// Usage:
// --------------------------------------------------------

function createPromise(progressCallback) {
  return new Promise(function(resolve, reject) {
    // do whatever you need and report progress to progressCallback(float)
  });
}

var wrapper = createMultiThreadProgressWrapper(3, mainCallback);

var promises = [
  createPromise(wrapper.getCallback(0)),
  createPromise(wrapper.getCallback(1)),
  createPromise(wrapper.getCallback(2))
];

Promise.all(promises);
0
Aleksey Gureiev

@Keith en plus de mon commentaire, voici une modification

(édité de façon détaillée, espérons-le)

// original allProgress
//function allProgress(proms, progress_cb) {
//  let d = 0;
//  progress_cb(0);
//  proms.forEach((p) => {
//    p.then(()=> {    
//      d ++;
//      progress_cb( (d * 100) / proms.length );
//   });
//  });
//  return Promise.all(proms);
//}

//modifying allProgress to delay 'p.then' resolution
//function allProgress(proms, progress_cb) {
//     let d = 0;
//     progress_cb(0);
//     proms.forEach((p) => {
//       p.then(()=> {
//         setTimeout( //added line
//           () => {
//                 d ++;
//                 progress_cb( (d * 100) / proms.length );
//           },       //added coma :)
//           4000);   //added line
//       });
//     });
//     return Promise.all(proms
//            ).then(()=>{console.log("Promise.all completed");});
//            //added then to report Promise.all resolution
// }

//modified allProgress
// version 2 not to break any promise chain
function allProgress(proms, progress_cb) {
    let d = 0;
    progress_cb(0);
    proms.forEach((p) => {
      p.then((res)=> {                        //added 'res' for v2
        return new Promise((resolve) => {     //added line for v2
          setTimeout(   //added line
              () => {
                    d ++;
                    progress_cb( (d * 100) / proms.length );
                    resolve(res);             //added line for v2
              },        //added coma :)
            4000);      //added line
        });                                   //added line for v2
      });
    });
    return Promise.all(proms
                   ).then(()=>{console.log("Promise.all completed");});
                   //added then chaining to report Promise.all resolution
}


function test(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
       console.log(`Waited ${ms}`);
       resolve();
     }, ms);
  });
}


allProgress([test(1000), test(3000), test(2000), test(3500)],
  (p) => {
     console.log(`% Done = ${p.toFixed(2)}`);
});

"Promise.all done" sortira avant tout message de progression

voici la sortie que j'obtiens

% Done = 0.00
Waited 1000
Waited 2000
Waited 3000
Waited 3500
Promise.all completed
% Done = 25.00
% Done = 50.00
% Done = 75.00
% Done = 100.00
0
user3617487