f1nn
f1nn

Reputation: 7057

How does callback exactly work in NodeJS?

I have a mongob with user object. Here's the schema:

{
    "_id": {
        "$oid": "50658c835b821298d3000001"
    },
    "email": "admin",
    "password": "admin",
    "id": 1
}

I wrote a simple demo (only main parts):

function findByEmail(email, callback) {
  db.collection("users", function(err, collection) {
      collection.find({}, function(err, users) {
          users.each(function(err, user) {
              if (user) {
                  if (user.email === email) {
                      //for a case, is we found user - return it
                      callback(null, user);
                  }
              }
          });
          //for a case, is we didn't find user - return null
          callback(null, null);
      });
  });
}

And a route for testing:

app.get('/test',function(req,res){
  findByEmail("admin", function(err, user){
    res.send(user);
  })
})

After lauching localhost:3000/test I get

Error: Can't set headers after they are sent.

If I comment line callback(null, null); I dont get this error. It seems that callback works... twice! How can it be? I supposed that if I if (user.email === email) { ... } works, and callback(null, user); is launched, function findbyEmail returns <user> to app.get, but callback works twice (for callback(null, null); also) even when if (user.email === email) { ... } is true.

Upvotes: 0

Views: 608

Answers (3)

lanzz
lanzz

Reputation: 43208

Let's refer to the function you pass to collection.find as outer and to the one you pass to users.each as inner (I have labeled them so in the code block below for clarity). Now, the problem is that when you invoke callback(null, user) in inner, the users.each loop continues to run, and will still proceed with testing all the remaining elements in the result; also, the outer function is still not finished, and will invoke your callback(null, null) callback after the users.each loop is done. You can't really just return after you find a user and invoke callback(null, user) as I previously suggested, as this will only terminate that single iteration of the users.each loop and will proceed with the next and all of the remaining elements; you need to define a flag indicating whether you have found the record already or not:

collection.find({}, function outer(err, users) {
    var found = false;
    users.each(function inner(err, user) {
        if (user && !found) {
            if (user.email === email) {
                //for a case, is we found user - return it
                callback(null, user);
                found = true;
            }
        }
    });
    //for a case, is we didn't find user - return null
    if (!found) {
        callback(null, null);
    }
});

Also note that you can do that search for a user record by email entirely in mongo and replace your whole logic with this:

collection.find({ email: email }, function(err, users) {
    callback(null, users.length? users[0]: null);
});

Upvotes: 0

chovy
chovy

Reputation: 75854

You callback still gets executed:

for ( var key in obj ) {
  foo();
}

foo();

Why would you expect the 2nd foo() not to be called?

You need to do return foo(); if you don't want to keep going.

Upvotes: 0

Juicy Scripter
Juicy Scripter

Reputation: 25938

You are calling callback(null, null) right after you iterate over results. So if you have some results - callback will be called twice:

  • one for handled results (which will send output)
  • and second time after iteration.

You should only call callback(null, null) if there is no results...

if (!users || users.toArray().length==0) callback(null, null);

Upvotes: 3

Related Questions