简体   繁体   中英

ES6 Promise.all progress

I have several promises that I need to resolve before going further.

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

Is there any way I can have the progress of the Promise.all promise?

From the doc, it appears that it is not possible . And this question doesn't answer it either.

So:

  • Don't you agree that this would be useful? Shouldn't we query for this feature?
  • How can one implement it manually for now?

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

You can use my npm package with an extended version of the native promise, that supports advanced progress capturing, including nested promises, out of the box Live sandbox

import { CPromise } from "c-promise2";

(async () => {
  const results = await CPromise.all([
    CPromise.delay(1000, 1),
    CPromise.delay(2000, 2),
    CPromise.delay(3000, 3),
    CPromise.delay(10000, 4)
  ]).progress((p) => {
    console.warn(`Progress: ${(p * 100).toFixed(1)}%`);
  });

  console.log(results); // [1, 2, 3, 4]
})();

Or with concurrency limitation ( Live sandbox ):

import { CPromise } from "c-promise2";

(async () => {
  const results = await CPromise.all(
    [
      "filename1.txt",
      "filename2.txt",
      "filename3.txt",
      "filename4.txt",
      "filename5.txt",
      "filename6.txt",
      "filename7.txt"
    ],
    {
      async mapper(filename) {
        console.log(`load and push file [${filename}]`);
        // your async code here to upload a single file
        return CPromise.delay(1000, `operation result for [${filename}]`);
      },
      concurrency: 2
    }
  ).progress((p) => {
    console.warn(`Uploading: ${(p * 100).toFixed(1)}%`);
  });

  console.log(results);
})();
const dataArray = [];
let progress = 0;

Promise.all(dataArray.map(async (data) => {
    await something();

    console.log('progress = ', Math.celi(progress++ * 100 / dataArray.length))
}))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM