findOne in loop of subdocuments. Callback closure problem.

71 views
Skip to first unread message

Francois Rioux

unread,
Oct 4, 2013, 12:44:50 AM10/4/13
to mongoo...@googlegroups.com
Hi group,
My challenge should be common but I can find it described in the forum, so I must using the wrong approach.

I have related objects. Let say Harvest and Harvest Details.  They really exist jointly, like Orders and OrderItems. And I'd rather ´save´ them at once, so a subdocument relation sounds appropriate

The (simplified) schemas:

var HarvestDetailsSchema = new Schema({
    amount: Number,
    name: String
});

/**
 * Harvest Event Schema
 */
var HarvestSchema = new Schema({
    harvesttime: {type: Date},
    user: { type: Schema.ObjectId,ref: 'User'},
    mac: String,
    details: [HarvestDetailsSchema]
});
 
The server side save function.

    harvest.save(function (err) {
        if (err) {... });

    var i = harvest.details.length;
    while (i--) {
        console.log(i);
        detail = harvest.details[i];
        Tree.findOne({ mac: detail.mac }, '_id',findTreeCB);
    }
});


In the while countdown loop I try to take some action (create or update other objects), based on the current <detail> object, based on i.  It does not have to be count down, the actions are independent.

The problem is by the time findTreeCB is called back, the while loop has completed and the <detail> object seen by the findTreeCB callback is the last value.  I understand the closure problem, but need a solution.

I think it should work if I could simply let the callback know about the current instance of <detail>.

I've tried to force some wrapper functions around the findOne and also the callback function, with no success.  I do not discard my code had problems but I suspect the issue is in the Mongoose library.

Is there a way I can pass a variable (here <detail>) to the calback function that is not the err or findOne result?

On the other had, I'm open to consider a different way of doing things.  I'm not to familiar with this async model and I might be thinking too much in a procedural way.  There are pre and post middleware that I see I could use.  But I do not yet see how I can work around the closure and the callback.

Thanks for any guidance.

Alessio Dione

unread,
Oct 4, 2013, 5:41:01 AM10/4/13
to mongoo...@googlegroups.com
I'm working with NodeJS and find async library perfect for this, ref: https://github.com/caolan/async
You can try the queue item https://github.com/caolan/async#queue or any other that applies.


On Friday, 4 October 2013 06:44:50 UTC+2, Francois Rioux wrote:
(...)

Joe Wagner

unread,
Oct 5, 2013, 9:43:19 PM10/5/13
to mongoo...@googlegroups.com
Hey Francois,

I agree with Alessio that the async library is a great solution.  With that said, you could get the results you are looking for with the following code:

    var closureFunc = function (detail) {
        return function (err, findOneResult) {
            if (err) { /* handle error */ }
            // now you can call your original callback with the detail object, or whatever you like...
            findTreeCB(detail)
        };
    };

    var i = harvest.details.length;
    while (i--) {
        console.log(i);
        detail = harvest.details[i];
        Tree.findOne({ mac: detail.mac }, '_id', closureFunc(detail));

Francois Rioux

unread,
Oct 6, 2013, 6:20:24 AM10/6/13
to mongoo...@googlegroups.com
Thank you @Alessio, Joe,

I haven´t retried with your options as in the meantime, I figured out that the .save of the Harvest model also implicitely triggers a .save function in the child HarvestDetails subdocument.  This is were I hooked a custom pre which did the job perfectly.  This also ends up being a better separation of concerns I believe.

Francois.
Reply all
Reply to author
Forward
0 new messages