merseyside
merseyside

Reputation: 65

Handling nested callbacks/promises with Mongoose

I am a beginner with Node.js and Mongoose. I spent an entire day trying to resolve an issue by scouring through SO, but I just could not find the right solution. Basically, I am using the retrieved values from one collection to query another. In order to do this, I am iterating through a loop of the previously retrieved results.

With the iteration, I am able to populate the results that I need. Unfortunately, the area where I am having an issue is that the response is being sent back before the required information is gathered in the array. I understand that this can be handled by callbacks/promises. I tried numerous ways, but I just haven't been successful with my attempts. I am now trying to make use of the Q library to facilitate the callbacks. I'd really appreciate some insight. Here's a snippet of the portion where I'm currently stuck:

var length = Object.keys(purchasesArray).length;
var jsonArray = [];

var getProductDetails = function () {
var deferred = Q.defer();
for (var i = 0; i < length; i++) {
    var property = Object.keys(purchasesArray)[i];
    if (purchasesArray.hasOwnProperty(property)) {
        var productID = property;
        var productQuery = Product.find({asin: 
        productQuery.exec(function (err, productList) {
        jsonArray.push({"productName": productList[0].productName,
                           "quantity": purchasesArray[productID]});
        });
    }
}

return deferred.promise;
};

getProductDetails().then(function sendResponse() {
    console.log(jsonArray);
    response = {
        "message": "The action was successful",
        "products": jsonArray
    };
    res.send(response);
    return;
 }).fail(function (err) {
     console.log(err);
    })
 });

I am particularly able to send one of the two objects in the jsonArray array as the response is being sent after the first element.

Update

Thanks to Roamer-1888 's answer, I have been able to construct a valid JSON response without having to worry about the error of setting headers after sending a response.

Basically, in the getProductDetails() function, I am trying to retrieve product names from the Mongoose query while mapping the quantity for each of the items in purchasesArray. From the function, eventually, I would like to form the following response:

 response = {
     "message": "The action was successful",
     "products": jsonArray
 };

where, jsonArray would be in the following form from getProductDetails :

jsonArray.push({
    "productName": products[index].productName,
    "quantity": purchasesArray[productID]
});

Upvotes: 1

Views: 261

Answers (1)

Roamer-1888
Roamer-1888

Reputation: 19288

On the assumption that purchasesArray is the result of an earlier query, it would appear that you are trying to :

  • query your database once per purchasesArray item,
  • form an array of objects, each containing data derived from the query AND the original purchasesArray item.

If so, and with few other guesses, then the following pattern should do the job :

var getProductDetails = function() {
    // map purchasesArray to an array of promises
    var promises = purchasesArray.map(function(item) {
        return Product.findOne({
            asin: item.productID // some property of the desired item
        }).exec()
        .then(function product {
            // Here you can freely compose an object comprising data from :
            // * the synchronously derived `item` (an element of 'purchasesArray`)
            // * the asynchronously derived `product` (from database).
            // `item` is still available thanks to "closure".
            // For example :
            return {
                'productName': product.name,
                'quantity': item.quantity,
                'unitPrice': product.unitPrice
            };
        })
        // Here, by catching, no individual error will cause the whole response to fail.
        .then(null, (err) => null); 
    });
    return Promise.all(promises); // return a promise that settles when all `promises` are fulfilled or any one of them fails.
};

getProductDetails().then(results => {
    console.log(results); // `results` is an array of the objects composed in getProductDetails(), with properties  'productName', 'quantity' etc.
    res.json({
        'message': "The action was successful",
        'products': results
    });
}).catch(err => {
    console.log(err);
    res.sendStatus(500); // or similar
});

Your final code will differ in detail, particularly in the composition of the composed object. Don't rely on my guesses.

Upvotes: 1

Related Questions