1

I am coding with Nodejs, MongoDB and Express. The code below looks for an user object with specific id in MongoDB. Once the user object is found, it retrieves favorite property of that object. The favorite property is an array whose each element is _id of a product. I tries to loop through this array. With each loop, I try to retrieve a product object from MongoDB and append this product object to new array (in my code below it's called "list"). I put some console.log() to check value of the list. It has value with each loop, but finally when I get the final one, it has null. I know the problem happens because I don't use properly deferred.resolve and deferred.promise. Please help me and explain deferred.resolve and deferred.promise works in the code. Thank you very much

function showBasket(user) {
var deferred = Q.defer();
var list =[];
db.users.findById(user, function (err, user) {
    if (err) deferred.reject(err);
    if (user) {

        var favorite = user.favorite;
        favorite.forEach(function(e){
        db.products.findById(mongo.helper.toObjectID(e), function(err, product){

                  if (err) deferred.reject(err);
                  if (product) {  
                    list.push(product);   
                    console.log(list);// list has value here                                   
                }

            })//end db.products.findById   

        })//end of forEach

    } //end of if
    console.log(list);// But finally, list has null value here
    deferred.resolve(list); 
});//end of db.users.findById

return deferred.promise;
}
4
  • Is db.products.findById asynchronous? If so, list is being tested and the deferred being resolved before any of the call backs requested in the .forEach function have been made. The callbacks push a product onto list, but this is some time later, after you logged list and found it to be empty. Commented Mar 6, 2017 at 1:54
  • I'm not aware of which library you're using to interact with MongoDB, but if you're using Mongoose (which doesn't quite look like it) you should really look into Query Population. Take a look at the docs: mongoosejs.com/docs/populate.html Commented Mar 6, 2017 at 2:03
  • @SilvestreHerrera, I don't use Mongoose. I used it before. It's great. This time, I just want to try with MongoDB only. Commented Mar 6, 2017 at 2:50
  • @Traktor53. Thank you. It's asynchronous. I still don't really understand about it. Do you think it's because asyn issue or closure issue ? Commented Mar 6, 2017 at 2:54

3 Answers 3

1

This is an asynchronous issue, but your forEach is useless because there is $in operator which will get all documents where the value of a field equals any value in the array :

db.products.find({
    "_id": {
        "$in": user.favorite
    }
}, function(err, products) {

    // here products is an array of product

    if (err) {
        console.log(err);
        deferred.reject(err);
    } else {
        console.log(products);
        deferred.resolve(products);
    }
})

The results is an array of products matching all the id in user.favorite array. If your items in user.favorite are not of type ObjectId, you may want to perform your mongo.helper.toObjectID(item) on each item before the query :

var favoriteArr = [];

for (var i = 0; i < user.favorite.length;i++)[
    favoriteArr.push(mongo.helper.toObjectID(user.favorite[i]));
}
// use favoriteArr with $in operator
Sign up to request clarification or add additional context in comments.

1 Comment

@Martel, Thank you. I tried your code but it sends back error 400 (Bad Request). I think something wrong with db.products.find({ "_id": { "$in": user.favorite } } query. I also tried with mongo.helper.toObjectID(item) but same error
1

Try This Your output is null becauase your foreach loop is async.. Please check below answer

function showBasket(user) {
    var deferred = Q.defer();
    var list = [];
    db.users.findById(user, function (err, user) {
        if (err) deferred.reject(err);
        if (user) {

            var favorite = user.favorite;
            // This function works like async loop (Recusrsive function)
            function uploader(i) {
                if (i < favorite.length) {
                    db.products.findById(mongo.helper.toObjectID(e), function (err, product) {

                        if (err) {
                            console.log(err);
                            deferred.reject(err)
                        };
                        if (product) {
                            list.push(product);
                            console.log(list);// list has value here    
                            uploader(i + 1)
                        }

                    })//end db.products.findById 
                }
                else {
                    console.log(response);
                     console.log(list);// This will be final result                        deferred.resolve(list);
                }
            }
            uploader(0)
        } //end of if

    });//end of db.users.findById

    return deferred.promise;
}

Comments

0

Modifications below demonstrates the concept of waiting to resolve the deferred promise until all the callbacks for retrieving product values have been made - the problem is an async issue.

There may be library routines which have the same functionality, there must be many ways of doing this, and be warned this code is untested:

function showBasket(user) {
var deferred = Q.defer();
var list =[];
db.users.findById(user, function (err, user) {
    if (err) deferred.reject(err);
    if (user) {

        var favorite = user.favorite;
        var callbacks = favorite.length; // how many call backs are expected
        if( callbacks == 0) {
            deferred.resolve( list); // or reject for reason "no products"
            return;
        }

        favorite.forEach(function(e){
        db.products.findById(mongo.helper.toObjectID(e), function(err, product){
                  --callbacks;
                  if (err) {
                      deferred.reject(err);
                  }
                  else {
                      if (product) {  
                          list.push(product);   
                          console.log(list);// list has value here                                   
                      }
                      if( callbacks == 0) {
                          console.log("all callbacks completed, resolve");
                          deferred.resolve(list);
                      }
                  }
            })//end db.products.findById   
        })//end of forEach
    } //end of if
});//end of db.users.findById

return deferred.promise;
}

Note that promises can only be resolved or rejected once. So if one product retrieval fails and the promise is rejected, a later "resolve" of the promise because all callbacks have come in is ignored.

Comments

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.