Reputation: 13
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.
Upvotes: 0
Views: 1769
Reputation: 11062
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
})
})
Upvotes: 1
Reputation: 27327
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 require
d 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);
});
Upvotes: 2