6

I am trying to call a function inside a for loop and the problem is that the function is called after the loop was finished.

Taking the below as an example, it prints to the console:
here1
here1
here2
here2

Instead of
here1
here2
here1
here2

report.forEach(item => {
  item.runs.forEach(run => {
    waComplianceBusiness(req, run.id, (err, res) => {
      const compliance = res.data.overviews[0].compliance;
      var failureList = [];

      compliance.forEach((rule, index) => {
        console.log('here1');
        waRuleOverview(req, run.id, rule.id, (err, res) => {
          console.log('here2');
          // handle the response
        });
      });
    });
  });
});

How can I fix this?

Please let me know if I need to provide additional information


Here is the complete code:

export default (req, callback) => {
  const report = req.body.webAudits;

  if(report.length > 0) {
    report.forEach(item => {
      item.runs.forEach(run => {
        waComplianceBusiness(req, run.id, (err, res) => {
          const compliance = res.data.overviews[0].compliance;
          if(compliance) {
            var failureList = [];
            compliance.forEach((rule, index) => {
              if(rule.pagesFailed > 0) {
                waRuleOverview(req, run.id, rule.id, (err, res) => {
                  const failedConditions = res.data.failedConditions;
                  const ruleName = res.data.ruleName;

                  failedConditions.forEach((condition, failedIndex) => {
                    const request = {
                      itemId: condition.conditionResult.id,
                      itemType: condition.conditionResult.idType,
                      parentId: condition.conditionResult.parentId,
                      parentType: condition.conditionResult.parentType
                    }
                    const body = {
                      runId: run.id,
                      ruleId: rule.id,
                      payload: request
                    }

                    waConditionOverview(req, body, (err, res) => {
                      const description = res.data.description;
                      const conditionValues = res.data.conditionValues[0];
                      var actualValue = conditionValues.value;

                      if(actualValue == "") {
                        actualValue = 'empty';
                      }

                      if(description.idType == "variable") {
                        var failureObj = {
                          ruleName: ruleName,
                          expected: description.name + ' ' + description.matcher + ' ' + description.expected[0],
                          actual: description.name + ' ' + description.matcher + ' ' + actualValue
                        };
                      }
                      else if(description.idType == "tag") {
                        var failureObj = {
                          ruleName: ruleName,
                          expected: description.name + '\n' + description.matcher,
                          actual: actualValue
                        };
                      }
                      failureList.push(failureObj);
                    });
                  });
                });
              }
              if(key + 1 == compliance.length) {
                console.log(failureList);
              }
            });
          }
        });
      });
    });
  }
}

These are the callback functions:

export function waComplianceBusiness(req, runId, callback) {
  const apiToken = req.currentUser.apiToken;
  const payload = {
    'Authorization': 'api_key ' + apiToken
  }

  const options = {
    'method': 'get',
    'gzip': true,
    'headers': payload,
    'content-type': 'application/json',
    'json': true,
    'url': 'api_url'
  }

  request(options, (error, response, body) => {
    callback(null, body);
  });
}

export function waRuleOverview(req, runId, ruleId, callback) {
  const apiToken = req.currentUser.apiToken;
  const payload = {
    'Authorization': 'api_key ' + apiToken
  }

  const options = {
    'method': 'get',
    'gzip': true,
    'headers': payload,
    'content-type': 'application/json',
    'json': true,
    'url': 'api_url'
  }

  request(options, (error, response, body) => {
    callback(null, body);
  });
}

export function waConditionOverview(req, body, callback) {
  const apiToken = req.currentUser.apiToken;
  const payload = {
    'Authorization': 'api_key ' + apiToken
  }

  const options = {
    'method': 'post',
    'gzip': true,
    'headers': payload,
    'body': body.payload,
    'content-type': 'application/json',
    'json': true,
    'url': 'api_url'
  }

  request(options, (error, response, body) => {
    callback(null, body);
  });
}

My goal is to return the failureList array after the loop over the compliance array is done

I found a similar question here but not sure if that would work in my case and I don't really know how to implement the promises

Vikash Chauhan
  • 792
  • 2
  • 9
  • 18
Valip
  • 4,440
  • 19
  • 79
  • 150
  • 4
    This is happening because your inner call (where `here2` is printed) runs _asynchronously_. This means that first all passes of `here1` are executed, each scheduling a future execution of `here2`, and then all the scheduled executions occur. This is what you're seeing. – salezica Aug 03 '17 at 13:30
  • I think this is happening because you're expecting your program to execute synchronously whereas callbacks work asynchronously. Take a look at async/await if you want it to wait for the methods to go in the order that you write them. – haakym Aug 03 '17 at 13:30
  • @slezica I'm a newbie to node.js and an example based on my code would help me a lot.. – Valip Aug 03 '17 at 13:34
  • I can't describe the solution (and why it works, the most important part) briefly in a comment, and I don't have the time to write it out now. It's not a quick fix. I upvoted the question so others will look at it. I can tell you this though: you can approach this problem using libraries such as [async](https://github.com/caolan/async) as shown [here](https://stackoverflow.com/questions/10390041/node-js-using-the-async-lib-async-foreach-with-object) – salezica Aug 03 '17 at 13:37
  • @C0dekid that doesn't solve it – Valip Aug 03 '17 at 13:38
  • @haakym I updated the question body, hopefully it's more clear now what I'm trying to achieve – Valip Aug 03 '17 at 17:08

2 Answers2

-1

The for loop executes the statements inside the scope sequentially. But it does not wait for the the function calls to complete, it continues with the next statement(i.e works asynchronously). That is why the result is as such. You can make it work synchronously using Promises or by using the async module.

As it is not clear what you are going to perform in the function call and what you want the statements to do, I am not able to suggest either of which. . asyn.each is usually preferred for making the for loop execute synchronously. And promises are used when you want to wait for the function to finish executing and then perform operation. You might want to look at their documentation

Promises|MDN

async.each

Thank you, Ragul

Community
  • 1
  • 1
Ragul Parani
  • 621
  • 7
  • 22
-1

If you want to do it in sequence use async.eachOfSeries

   async.eachOfSeries(report, function(item, index, eachOfCallback1){

       async.eachOfSeries(item.runs, function(run, index, eachOfCallback2){

          waComplianceBusiness(req, run.id, (err, res) => {

              var failureList = [];
              async.eachOfSeries(compliance, function(rule, index, eachOfCallback3){

                 console.log('here1');
                 waRuleOverview(req, run.id, rule.id, (err, res) => {
                     console.log('here2');
                     return eachOfCallback3(err);
                 });

               }, function(err){
                 if(err)
                    return eachOfCallback2(err);
                 else return eachOfCallback2();
               });

           });
        }, function(err){
           if(err)
              return eachOfCallback1(err);
           else return eachOfCallback1();
        })
    }, function(err){
       // handle final response  
    })

If you want to optimise the process take a look at async.parallel

Daphoque
  • 4,421
  • 1
  • 20
  • 31