How to loop over object & return mongoDB entry for each item?

I am having difficulties looping over an object of constituency data, finding existing entries in a MongoDB and doing something with them. It always ends up being the same entry being passed to be found in the DB over and over again.

I am assuming this is a problem of scope and timing.

My code:

for (key in jsonObj) {

        var newConstituent = new Constituent({
            name : jsonObj[key]["Name"],
            email : jsonObj[key]["Email"],
            social : {
                twitter: {
                    twitter_handle : jsonObj[key]["Twitter handle"],
                    twitter_id : jsonObj[key]["User id"],
                    timestamp : jsonObj[key]["Timestamp"]
                }
            }
            });
        console.log(jsonObj[key]["Email"]); // this is fine here! 

        Constituent.findOne({ email : jsonObj[key]["Email"] }, function(err, constitutents){
            console.log(jsonObj[key]["Email"]); // here it's always the same record
            if (err) {
                console.log(err)
            }
            if (constitutents === 'null') {
                console.log("Constituent not found. Create new entry .. ");
               // console.log(newConstituent);
                newConstituent.save(function (err) {
                    if (err) {
                        console.log('db save error');
                    }
                });
            } else {
                console.log("Constituent already exists .. ");
            }
        });

    }

I have a suspicion that the for loop finishes sooner than .findOne() is executing and therefor always and only gets the last item of the object passed to find.

Could someone point me into the right direction?

A couple of this.

  1. Don't use for ... in, especially in node. You can use Object.keys() and any of the array methods at that point. for ... in can include values you don't wish to loop over unless you're using hasOwnProperty since it'll include values from the prototype chain.

  2. The reason the email is the same is that you're just printing out your query again. jsonObj is included in the scope of your callback to findOne since you're not re-declaring it inside the findOne callback. So whatever the value of key happens to be (my guess is that it's the last one in your list) when the callback is invoked is the email you're getting. Since, in javascript, inner function scope always includes, implicitly, the scope of the surrounding context, you're just accessing the jsonObj from your enclosing scope.

    To clarify about this point, your for ... in loop is synchronous -- that is the interpreter finishes running all the instructions in it before it will process any new instructions. findOne, how ever is asynchronous. Very simply, When you call it in this loop, it's not actually doing ANYTHING immediately -- the interpreter is still running your for ... in loop. It is, however, adding more tasks to the execution stack to run after it's finished your loop. So the loop is finished, AND THEN your callbacks will start to execute. Since the for ... in loop is totally finished, key is set to whatever the final value of it was. So, for example, if it's last value was foo that means EVERYTIME your callback is invoked, you will be printing out jsonObj.foo since the for ... in loop is already complete.

    So it's like you asked your friend to say the letters from A to J, and you left the room to do 10 things. To do something. He totally finished going to J since that is much faster than doing 1 of the 10 things you're doing. Now every time you're done doing one of your things, you come back and say "what's the latest letter you said". The answer will ALWAYS be J. If you need to know what letter he is on for each task you either need to get him to stop counting while you're doing it or somehow get the information about what letter corresponds with the number of task that you're performing.

    Having them wait is not a good idea -- it's a waste of their time. However, if you wrap your findOne in a new function where you pass in the value of key, this would work. See the updated code below.

  3. I'm not sure about your data but findOne will return one record. You're putting it into a variable with a plural (constitutents). From reading your code I would expect back a single value here. (It might still be wrapped in an array however.)

Since you're calling findOne and assigning the results of the find operation to constituent, you should be examining that object in the console.log.

e.g.

console.log(constitutents.email); // or console.log(constitutents[0].email)

rather than

console.log(jsonObj[key]["Email"]);

(Assuming email is a property on constituants).

You might just try logging the constituants entirely to verify what you're looking for.

The reason this following code will work is that you're passing the current value of key to the function for each invocation. This means there is a local copy of that variable created for each time you call findConstituent rather than using the closure value of the variable.

var newConstituent;


function findConstituent(key){
    Constituent.findOne({ email : jsonObj[key]["Email"] }, function(err, constitutents){
        console.log(jsonObj[key]["Email"]); // here it's always the same record
        if (err) {
            console.log(err)
        }
        if (constitutents === 'null') {
            console.log("Constituent not found. Create new entry .. ");
           // console.log(newConstituent);
            newConstituent.save(function (err) {
                if (err) {
                    console.log('db save error');
                }
            });
        } else {
            console.log("Constituent already exists .. ");
        }
    });

}


for (key in jsonObj) {
    newConstituent = new Constituent({
        name : jsonObj[key]["Name"],
        email : jsonObj[key]["Email"],
        social : {
            twitter: {
                twitter_handle : jsonObj[key]["Twitter handle"],
                twitter_id : jsonObj[key]["User id"],
                timestamp : jsonObj[key]["Timestamp"]
            }
        }
    });
    findConstituent(key);
}