63

I am running a forEach loop on an array and making two calls which return promises, and I want to populate an object say this.options, and then do other stuff with it. Right now I am running into the async issue if i use the following code sample and i get into the then function first.

$.when.apply($, someArray.map(function(item) {
    return $.ajax({...}).then(function(data){...});
})).then(function() {
    // all ajax calls done now
});

This is working code below, but it only works for the first element in the array, because I call the resulting function in the .then of the response. I want to do all the fetch first for all elements of the array and then call the resulting function to do something.

array.forEach(function(element) {
    return developer.getResources(element)
        .then((data) = > {
            name = data.items[0];
            return developer.getResourceContent(element, file);
        })
        .then((response) = > {
            fileContent = atob(response.content);
            self.files.push({
                fileName: fileName,
                fileType: fileType,
                content: fileContent
            });
            self.resultingFunction(self.files)
        }).catch ((error) = > {
            console.log('Error: ', error);
        })
});

How do i populate the self.files object after the forEach loop is complete, and then call the resulting function with the files object?

2
  • 3
    Why forEach when you clearly need map? Commented Jul 13, 2016 at 21:51
  • @YuryTarabanko I used a map and that didn't work either. I also tried the approach in the answer but with a $.when.apply($, promises).then(), but didn't work either. I think it did not work because I was did not do it the ES6 way.
    – xyzzz
    Commented Jul 13, 2016 at 23:11

6 Answers 6

119

Promise.all() will be helpful here:

var promises = [];

array.forEach(function(element) {
    promises.push(
        developer.getResources(element)
            .then((data) = > {
                name = data.items[0];
                return developer.getResourceContent(element, file);
            })
            .then((response) = > {
                fileContent = atob(response.content);
                self.files.push({
                    fileName: fileName,
                    fileType: fileType,
                    content: fileContent
                });
            }).catch ((error) = > {
                console.log('Error: ', error);
            })
    );
});

Promise.all(promises).then(() => 
    self.resultingFunction(self.files)
);

This starts the AJAX call for each of the items, adds the result of each call to self.files once the call is complete and calls self.resultingFunction() after all calls have been completed.

Edit: Simplified based on Yury Tarabanko's suggestions.

13
  • 2
    Should be Promise.all, not Promises (plural). Commented Jul 13, 2016 at 21:53
  • 2
    Why are you manually creating a Promise? It all looks like getResources already returns one Commented Jul 13, 2016 at 21:57
  • @YuryTarabanko Out of curiosity, if you would push the promises returned from getResources directly to the promises array instead of wrapping it in your own, is it guaranteed that all individual finalization handlers are called first before the finalization handler of Promise.all()? If not, creating your own promises seems necessary. Commented Jul 13, 2016 at 22:06
  • @TimoSta Not directly from getResources but the last promise you get in chain getResources().then(fn1).then(fn2).catch(). Every time you call then you get a new promise. And sure fn1 will be called before fn2 and before finalization handler. Simply because promises will pass the result of fn1 to fn2 and then to finalization handler (as corresponding array element). Commented Jul 13, 2016 at 22:16
  • 1
    I have a doubt. The list of all promises might not be in sequential order since array.forEach() is asynchronous. This might work for usual cases but what if one of the items in forEach delays response, then will the order of the promises in the promise array remain same ?
    – informer
    Commented Sep 28, 2017 at 20:30
49

Just a slight variation of the accepted solution above would be:

var promises = array.map(function(element) {
      return developer.getResources(element)
          .then((data) = > {
              name = data.items[0];
              return developer.getResourceContent(element, file);
          })
          .then((response) = > {
              fileContent = atob(response.content);
              self.files.push({
                  fileName: fileName,
                  fileType: fileType,
                  content: fileContent
              });
          }).catch ((error) = > {
              console.log('Error: ', error);
          })
});

Promise.all(promises).then(() => 
    self.resultingFunction(self.files)
);

I used Array.map instead of Array.forEach, which means I don't need to create an empty array first, I just re-use the existing one.

2
  • Could you highlight what you have changed so that we don't have to read every line? Commented Apr 1, 2019 at 23:53
  • 5
    Have added a new sentence at the end explaining what I did.
    – JackDev
    Commented Apr 3, 2019 at 9:15
10

You can use .map as a cycle to start Promise request for each element of array and return new Array of these Promises. Also I used destructurization, but not sure that we need it here.

await Promise.all([...arr.map(i => "our promise func"())]);
1
  • 1
    While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. Commented Feb 2, 2021 at 11:12
4

The following code is simple understanding of sync using Promise.

let numArr = [1,2,3,4,5];
let nums=[];

let promiseList = new Promise(function(resolve,reject){
  setTimeout(()=>{
        numArr.forEach((val)=>{
        nums.push(val);
    });
    resolve(nums);
 },5000)
})


Promise.all([promiseList]).then((arrList)=>{
  arrList.forEach((array)=>{
    console.log("Array return from promiseList object ",array);    
  })

 });

Above example will hold array nums for 5 sec. and it will print on console after it release.

2

You might look at this answer to a similar question for an excellent hint. The solution given there uses Array#reduce() to avoid having to accumulate all of the Promises before doing any of the work rather than using Promise.all().

0

Here's a version using async/await. If the chain of promises is longer it might be neater.

const promises = array.map(async(element) {
         try{
             const data = await developer.getResources(element)
             const name = data.items[0];
             return developer.getResourceContent(element, file);
                  })
                  .then((response) = > {
                      fileContent = atob(response.content);
                      self.files.push({
                          fileName: fileName,
                          fileType: fileType,
                          content: fileContent
                  });
              })
        }
        catch (error) {
            console.log('Error: ', error);
        }
    });
    
    await Promise.all(promises) 
    self.resultingFunction(self.files)

Not the answer you're looking for? Browse other questions tagged or ask your own question.