ES6 Promise.all progress

余生长醉 提交于 2019-11-28 21:30:32

I've knocked up a little helper function that you can re-use.

Basically pass your promises as normal, and provide a callback to do what you want with the progress..

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)}`);
});

You can add a .then() to each promise to count whos finished. something like :

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);
});

This has a few advantages over Keith's answer:

  • The onprogress() callback is never invoked synchronously. This ensures that the callback can depend on code which is run synchronously after the call to Promise.progress(...).
  • The promise chain propagates errors thrown in progress events to the caller rather than allowing uncaught promise rejections. This ensures that with robust error handling, the caller is able to prevent the application from entering an unknown state or crashing.
  • The callback receives a ProgressEvent instead of a percentage. This eases the difficulty of handling 0 / 0 progress events by avoiding the 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)
      ) 
    })
  );
};

Note that ProgressEvent has limited support. If this coverage doesn't meet your requirements, you can easily polyfill this:

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

@Keith in addition to my comment, here is a modification

(edited to fully detail hopefuly)

// 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 completed" will output before any progress message

here is the output that I get

% 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

Here's my take on this. You create a wrapper for the progressCallback and telling how many threads you have. Then, for every thread you create a separate callback from this wrapper with the thread index. Threads each report through their own callback as before, but then their individual progress values are merged and reported through the wrapped callback.

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!