dreamcod3r dreamcod3r - 2 months ago 10
Javascript Question

Doing something as promises get fulfilled using es6 Promise.all()

As I understand, I can asynchronously resolve a bunch of promises with Promise.all(array). However, the .then() will run only after all those promises have been resolved. I'd like to know how I can do things as the promises get resolved.

For example, I am trying to load all the paragraphs of an article, and I want to load them asynchronously using Promise.all() so that the network requests all fire at once. However, 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 but if paragraph 3 done loading and 2 is not, I want 3 to wait for 2 before rendering to the page. I want this behavior to happen for all the paragraphs I am loading so that I don't have to wait for each one to finish loading before I load the next one.

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

How can I implement this kind of behavior?

For now I am just trying to learn, so please be kind and if you can give me an explanation I'd appreciate it! I am trying to code something like it here but 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? (I'm not sure if this is the best way to emulate this?)

Thanks!

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