Question

I have a mongoose model schema set up with something like the following.

var orderSchema = new Schema ({
 _invoices: [{ type: Schema.ObjectId, ref: 'invoice'},
 _discounts: [{ type: Schema.ObjectId, ref: 'discount'},
 _client: String

});

orderSchema.methods.totalInvoiced = function (cb) {
 this.populate('_invoices', function (err, order) {
  cb(err, _.reduce(_.pluck(order._invoices, 'amount'), function (a, b) {
    return a+b;
     }, 0);
     }

};

orderSchema.methods.totalDiscount = function (cb) {
  this.populate('_discounts', function (err, order) {
  cb(err, _.reduce(_.pluck(order.discounts, 'amount'), function (a, b) {
    return a+b;
     }, 0);
     }

};

Now I want to grab a collection of orders but I would like to include the "totalInvoiced" and the "totalDiscount" as an additional property on each document in the returned collection. I understand this might be a case for "totalInvoiced" to be a virtual property but I don't always want it to be included. Here is how I tried it but I feel like there is probably a better way to do this.

Order.find({}, function (err, orders) {
     // for each order calc totals and add to document as two new properties
     _.each(orders, function (order) {
       async.parallel({
         invoice: function (cb) {
           order.totalInvoiced(cb);
         },
         discount: function (cb) {
           order.totalDiscount(cb);
         }
       }, function (err, result) {
        order.totalInvoiced = result.invoice;
        order.totalDiscount = result.discount;
       }

     });

     return orders;

    });

My question is what is the best way to perform a query for a collection but also execute some async methods on each document as part of the query, or is the way I'm doing it by iterating the results of the query the right way to do this. Perhaps with a querystream

Was it helpful?

Solution

_.each() is not asyncronous, so you'll have a hard time continue execution when all of the totals are populated. Also, if you have no control over how many orders the Order.find() will return, you can get some serious performance problems by not throttling the population with a limit.

You can try something like this:

Order.find({}, function (err, orders) {
    // populate max 15 orders at any time
    async.mapLimit(orders, 15, function (order, cb) {
        async.parallel({
            invoice: function (cb) {
                order.totalInvoiced(cb);
            },
            discount: function (cb) {
                order.totalDiscount(cb);
            }
        }, function (err, result) {
            order.totalInvoiced = result.invoice;
            order.totalDiscount = result.discount;
            return cb(null, order);
        });
    }, function (err, orders) {
        console.log('Done!');
    });
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top