0

I recently started with Mongoose and Node.js and I have some difficulties in handling the result of an asynchronous Mongoose query. I have a collection called 'Groups' and a group can hold members located in a seperate collection called 'Members'. To accomplish this each group has a field named 'members' which is an array of ObjectId's. In addition each member has a profile which is refered to by an ObjectId. So each member has a field named 'profile' and the profiles are stored in a collection called 'Profiles'.

Now I want to get a list of profiles in a given group. Therefore I wrote this code:

// Function that returns a profile by a given objectId
function getprofile(profileId, profile) {
  Profile
  .findById(profileId)
  .exec(function(err, res) {
    if(err) { return null; }
    if(!res) { return null; }
    return res;
  });
}

// Main function that returns a list of all (unique) profiles from members within a group
Group
.findById(req.params.id)
.populate('members')
.exec(function(err, group) {
  if(err) { return handleError(res, err); }

  var tmpProfiles = [];
  var allProfiles = [];
  for(var i=0; i<group.members.length; i++) {
    var memberProfile = group.members[i].profile;

    // Check if the member's profile is already in the array
    if(objectIndexOf(tmpProfiles, memberProfile) == -1) { 
        tmpProfiles.push(memberProfile);
        allProfiles.push(getprofile(memberProfile)); // add the profile to the array of profiles
    }
  }
  return res.json(allProfiles); // this returns an empty array
});

The problem is that the 'Profile.findById' function and the 'Group.findById' function are both asynchronous and for this reason the function returns an array with only 'null' values. I know that I can solve this by using the 'Async' library but that is not an option for me so I have to go through the 'callback hell'. Can someone put me in the right direction by solving this problem? I already found some solutions which uses recursive calling of functions but I have no idea how to implement it in this case.

4
  • 1
    One word: Promises Commented Jul 13, 2015 at 18:27
  • 2
    Not possible. getProfile will never return the value you are looking for. You'll need to do some kind of callback chain or promise work. Commented Jul 13, 2015 at 18:27
  • out of curiosity why is async not an option? IMO its by far the cleanest solution Commented Jul 13, 2015 at 19:20
  • @Plato: because I want to minimize the use of external dependencies and I know it can be solved by the use of callback chaines for example. Commented Jul 13, 2015 at 22:37

2 Answers 2

2

You can take advantage of the fact that mongoose methods return Promises.

Note: you will need to either be using Node >= v0.12 or have a Promise library required in the module.

// Function that returns a profile by a given objectId
function getprofile(profileId, profile) {
    return Profile.findById(profileId); // return the Promise of this `findById` call
}

// Main function that returns a list of all (unique) profiles from members within a group
Group
    .findById(req.params.id)
    .populate('members')
    .then(function(group) {
        var tmpProfiles = [];
        var promises = []; // store all promises from `getprofile` calls

        for(var i = 0; i < group.members.length; i++) {
            var memberProfile = group.members[i].profile;

            // Check if the member's profile is already in the array
            if(objectIndexOf(tmpProfiles, memberProfile) == -1) { 
                tmpProfiles.push(memberProfile);
                promises.push(getprofile(memberProfile)); 
            }
        }

        return Promise.all(promises);
    })
    .then(function(allProfiles) {
        res.json(allProfiles);
    })
    .catch(function(err) {
        //todo: handle error
        console.log(err);
    });
Sign up to request clarification or add additional context in comments.

3 Comments

thanks for your answer. The code you posted here is working fine. I'm wondering if you also know a solution that uses chained callbacks?
Yes, however that solution is much less desirable! If this works, it is a much more succinct solution than one that uses callbacks and avoids what you yourself called the 'callback hell'.
Thanks for your reply. As I already mentioned @Plato I will continue using this solution you provided :).
1

I support your learning by doing callback hell without caolan/async!
Here's an async answer anyway, to compare to your OP and the promises answer

Group...exec(function(err, group){
  async.waterfall([
    function(done1){
      var tmpUnique = [];
      async.filter(group.members, function(member, done2){
        var isUnique = (tmpUnique.indexOf(member.profile) === -1);
        if(isUnique){ tmpUnique.push(member.profile) };
        done2(isUnique);
      }, function(err, uniqueMembers){
        done1(err, uniqueMembers);
      })
    },
    function(uniqueMembers, done1){
      async.map(uniqueMembers, function(member, done2){
        getProfile(member.profile, done2);
        // rewrite getProfile(id, callback){} to callback(err, profile)
        // and maybe rename the key member.profile if its an id to get the real profile?
      }, function(err, uniqueProfiles){
        done1(err, uniqueProfiles)
      });
    },
  ], function(err, uniqueProfiles){
    //callback(err, uniqueProfiles) or do something further
  })
})

1 Comment

thanks for your answer. I appreciate your solution by using the async library. Because of the fact that I'm using Node 0.12.4 I will continue with the promises solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.