1

I'm having a little trouble with node's asynchronous nature, and I can't figure out how to solve this one.

I'm trying to run an entire promise chain once for each item in a collection. It works perfectly if I only have one item in the collection - but once I add a second item, the results get weird.

I'm certain this has something to do with the loop running asyncronously and I need something like promise.all, but I can't.... figure.. it out.

Basically, I have a list of reports in a config file. Each report has a path (and some other info). That looks like this:

var reportsCollection = 
{
    "reportname1" : {
        "filename" : "filename1",
        "otheritem" : "etc..."
    }, 
    "reportname2" : {
        "filename : "filename2", 
    }
}

For each report in that Collection, I want to run a promise chain.

  //Will loop through twice, once with report = "reportname1", once with report = "reportname2"
for(var report in reportsCollection){ 

    console.log("Starting " + report);

    self.getReportRows(report, function(retVal){

        //does some stuff turning retval into rows  
        return rows;

    }).then(function(rows){

        //If I check here,the right number of rows is returned for each report - I get two different values. 
        console.log(rows.length + "returned"); 

        //The issue is here - the report variable does not refer to the current report, even though we are completing the entire promise chain once for each report. So I get report2 twice. 
        //I don't understand - should this entire thing not run once for each "report"? Why is it running the first section for each report and then
        // running this section multiple times for the last report?!

        console.log("uploading "+ rows.length + "rows for " + report + " data to" + reportsConfig[report]["filename"]); //<- always displays reportname2/filename2


    })

}); // end reportsConfig for loop

This will display something like:

Starting reportname1         //returns 4 rows
Starting reportname2         //returns 18 rows
uploading 4 rows for reportname2 to filename2
uploading 18 rows for reportname2 to filename2

Can someone help me out? I'm expecting result like

Starting reportname1
Starting reportname2
uploading 4 rows for reportname1 to filename1 
uploading 18 rows for reportname2 to filename2
jhpratt
  • 6,841
  • 16
  • 40
  • 50
Rachel Fee
  • 83
  • 1
  • 1
  • 6

1 Answers1

2

When you use var to declare a variable in the a loop each iteration of the loop uses the same variable. This is not normally a problem, but it becomes a problem with async functions because by the time the async function returns and tries to use that variable, it has already been changed to the last value of the loop. The entire loop runs before any of your promises return, so report is left at the last value of the loop: 'reportname2'

With ES6 it's a simple fix, simply use let instead of var and each iteration of the loop will get it's own value:

for(let report in reportsConfig){ // .. etc }
Mark
  • 90,562
  • 7
  • 108
  • 148
  • WOW that worked. What a stupidly easy answer. I've never had a reason to use let previous to this. Thanks! – Rachel Fee Nov 09 '17 at 20:51
  • @KevinB Link? I've been searching for a solution to a promise chain inside a for loop for like... three hours. – Rachel Fee Nov 09 '17 at 20:57
  • That's why you didn't find anything, the promise chain is irrelevant, it's the fact that it gets executed asynchronously. If you replaced the promise with a setTimeout or setImmediate you'd have the same problem. – Kevin B Nov 09 '17 at 20:58