Davevmb
Davevmb

Reputation: 85

NodeJS : callback and Async calls

I've been staring at my screen for too long and can't seem to figure this out... Basically i do 3 query's using mongoose, each one building on the result of the other one. For some reason the parent call finished before the child can complete it's tasks. This is my code, why are the parents not waiting to continue untill the child has finished the work? How can i make it so the code runs as i want it to :-)

I have been told to use promises, but it looks so complicated to use and i assume the use of callbacks would be enough to handle async calls or am i wrong?

var ctr = 0; // counter 1
var ct2 = 0; // counter 2

//Array to be used for the header of the csv file
var mainArr = [["Personeelsnummer", "Chauffeur", "Week 1", "Week 2", "Week 3", "Week 4"]];

    // Find all users in the database
    User.find({}).exec(function (err, users){
        // forEach user, push some basic data into tempArr
        // and lookup planned weeks (geplandeWeken) using another mongoose function
        users.forEach(function(user){
            var tempArr = [];
            tempArr.push(user.personeelsnummer);
            tempArr.push(user.fullName);

            user.geplandeWeken.forEach(function(week1){
                Week.findById(week1, function(err, foundWeek){
                    tempArr.push(foundWeek.week);

                    ctr++;
                    if(ctr === user.geplandeWeken.length){
                        mainArr.push(tempArr);
                        console.log(mainArr);
                    }
                });
            });
            ct2++;
        }); 

        if(ct2 === users.length){
            console.log(ct2);
            csv.write(mainArr, {headers:true}).pipe(res);           
        }       
    });

So long story short : users.forEach finishes before user.geplandeWeken.forEach is able to do it's job. The csv file is send to the user before any data could be collected and stored into the csv file.

Upvotes: 1

Views: 66

Answers (2)

Aashish Ailawadi
Aashish Ailawadi

Reputation: 206

First of all you have written the code synchronously that is why it is not waiting for your dependencies to resolve and continue after.

For this you'll be wanting to use callbacks or Promises. I prefer the latter as some complex codes when written witch callbacks might result in callback hell.

For reference about promise : Promises

So as per your requirement you can use promises like this: Install bluebird through npm(I prefer it , you can use any other package also), and use it in your main file as

var Promise = require("bluebird");
mongoose.Promise = Promise;

For making custom functions with promises you can use the following

function sample(required Params){
  return new Promise(function(resolve,reject){
   //do things you want to perform
   resolve(params you want to sendBack)//if everything has worked correctly
   reject(params you want to sendBack)// if something goes wrong
  };
}

As per you code I have modified it with promises the best I could understand

var ctr = 0; 
var ct2 = 0;

var mainArr = [["Personeelsnummer", "Chauffeur", "Week 1", "Week 2", "Week 3", "Week 4"]];
User.find({})
.then(function(users){
  for(user in users){
   var tempArr = [];
   tempArr.push(user.personeelsnummer);
   tempArr.push(user.fullName);
   for(week1 in user.geplandeWeken){
    Week.findById(week1)
    .then(function(foundWeek){
      tempArr.push(foundWeek.week);
      ctr++
      if(ctr === user.geplandeWeken.length){
          mainArr.push(tempArr);
          console.log(mainArr);
      }
    });
   }
   if(ct2 === users.length){
      console.log(ct2);
      csv.write(mainArr, {headers:true}).pipe(res);
   }
 }
})
.catch(function(err){
 //this is the common error handler
 //code for handling error
});

And personally since you are using json based langauage i.e. javasacript try using your tempArr as json, as it will give you more flexibillity and less changing in the future if you need to add some new values to your tempArr variable or you decide to shuffle the order of values stored inside the variable

var tempArr = {};
tempArr[keyName]=keyValue;

And access it like this tempArr.keyName;

For converting a for loop into asynchronous one use the following approach

function asyncFor(i){
   if(i < users.length){
     let user = users[i];
     //perform your rest of the actions and execute the last statment
     asyncFor(i++);
   }else{
     return;
   }
}asyncFor(0);

Hope this answers your query

Upvotes: 1

posit labs
posit labs

Reputation: 9431

Week.findById is asynchronous. It doesn't block code execution, and continues to the next instruction.

If you need to wait for many asynchronous tasks to complete before continuing, then you could use Promises.

var userPromises = users.map(user => {
    return new Promise((resolve, reject) => {
        // do something async
        // resolve(value) when complete 
    })
})

Promise.all(userPromises).then(results => {
    // do something with results
})

Upvotes: 0

Related Questions