dreamcod3r dreamcod3r - 2 months ago 7
Javascript Question

Perform actions as promises get fulfilled using Promise.all()

I can asynchronously resolve a bunch of promises with

Promise.all(array)
. However
.then()
will run only after all those promises have been resolved. How I can perform actions as the promises get resolved?

For example, I want to load all paragraphs from an article asynchronously using
Promise.all()
- This way the network requests all fire at once. If paragraph 1 is done loading, I want it to render to the page - but only if it is done loading before paragraph 2, then I want paragraph 2 to load. If paragraph 3 is done loading and 2 is not, I want 3 to wait for 2 before rendering to the page. And so on.

I tried something like this but I don't know what to do next:

var getStuff = function(number, time){
return new Promise(function(resolve, reject){
window.setTimeout(function(){resolve(`${number} - Done.`)}, time);
});
};

Promise.all([ getStuff(1, 200),
getStuff(2, 100),
getStuff(3, 250),
getStuff(4, 200),
getStuff(5, 300),
getStuff(6, 250),
getStuff(7, 5000)])
.then(function(data){
console.log(data);
});


How can I get the console log of the data to happen one after another - without resolving each promise with a
then()
before making the next request? Is there a better way to do this?

Answer

Basically I am trying to get the best of both worlds where I fire the requests asynchronously, but do things as they happen.

You cannot achieve this order using Promise.all because it waits for everything to resolve before doing anything.

What you need to do is create the promises individually and fire of their requests in parallel:

// create promises and make parallel (concurrent) requests
const s1 = getStuff(1, 200);
const s2 = getStuff(2, 100);
const s3 = getStuff(3, 250);
// ...

And then create a reaction chain on how to process them (row 1 before row2, row 2 before row 3, etc.):

// create a chain of reaction order to the results of parallel promises
s1
  .then(render)    // s1 resolved: render results
  .then(() => s2)  // chain s2
  .then(render)    // s2 resolved: render results
  .then(() => s3)  // chain s3
  // ...
  .then(() => {    // chain another function at at the end for when all promises resolved
    // all promises resolved (all data was rendered)
  }

Since you need to react to promise results in the same order you make the requests in, here's an example of how you can change your getStuff function a bit and dynamically chain the reactions using Array.prototype.reduce:

var times = [200, 100, 250, 200, 300, 250, 5000];

var getStuff = function(time, index) { // swap the order of arguments so number is the index passed in from Array.map
  return new Promise(function(resolve, reject){
    window.setTimeout(function(){resolve(`${index + 1} - Done.`)}, time); // use index + 1 because indexes start at 0
  });
};

times
  // map each time to a promise (and number to the index of that time + 1) and fires of a request
  .map(getStuff)  
  // dynamically build a reaction chain for the results of promises
  .reduce((chain, promise) => {
    return chain
      .then(() => promise)
      .then(render)
  }, Promise.resolve())
  .then(() => {
    // all promises resolved (all data was rendered)
  });