3

I am using the excellent caolan "async" module for nodejs:

I have this code:

exports.manageComments = function(req, res) {
    var toDeleteIds = [];
    var deleteFunctions = [];
    if (req.body.doDelete) {
        toDeleteIds = req.body.doDelete;
    }
    var i;
    for ( i = 0; i < toDeleteIds.length; i++ ) {
        var deleteFunction = function(callback) {
            var id = toDeleteIds[i];
            console.log(id);
            Comment.findOne({_id:id}, function(err, found) {            
                if (!err) found.remove(callback);
            });
        }
        deleteFunctions.push(deleteFunction);
    }
    async.parallel(
        deleteFunctions,
        function(err,results) {
            exports.comments(req, res); //render a view
        }
    );
};

My array contains two elements but console.log() keeps telling me "undefined".

What am I missing?

3 Answers 3

12

Your problem is with:

    var deleteFunction = function(callback) {
        var id = toDeleteIds[i];

because at the time each callback function is executed, i will have the same value as toDeleteIds.length. A closure doesn't "trap" the value that an outer variable had at the time it was created; it "traps" a reference to whatever value the outer variable has at the time it's executed (which in this case won't be until well after your for loop has finished.

In order to "trap" the value of i at the time you create your callback function, you need to make i a parameter of a function that you call to create your callback function. You need something like

    var deleteFunction = makeDeleteFunction(i, callback);

And then create a separate function outside the callback:

function makeDeleteFunction(i, callback) {
    return function(callback) {
        var id = toDeleteIds[i];
        console.log(id);
        Comment.findOne({_id:id}, function(err, found){            
            if (!err) found.remove(callback);
        });
     };
}
Sign up to request clarification or add additional context in comments.

1 Comment

I had this suspect! You are really good at explaining it! Thank you 1000 times! :D I'll accept the answer later, let me first try it!
4

ebohlman has correctly identified the problem. However I still think creating an array of closures is hugely inefficient and unnecessary. Here's shorter, easier code to achieve the same with a single function:

exports.manageComments = function(req, res) {
    var toDeleteIds = [];
    if (req.body.doDelete) {
        toDeleteIds = req.body.doDelete;
    }

    var deleteFunction = function(id, callback) {
        console.log(id);
        Comment.findOne({_id:id}, function(err, found) {            
            if (!err) found.remove(callback);
        });
    }

    async.forEach(toDeleteIds, deleteFunction, function(err,results) {
        exports.comments(req, res); //render a view
    });
};

2 Comments

Finally I adopted your solution, thank you very much! However this question was just to know how the "closures" mechanism works.
@zzen "results" aren't passed at the end unfortunately
2

Looking at this another way, if you don't need to fire the Mongoose remove middleware on each Comment doc being removed, you can remove all identified comments in one go:

Comment.remove({_id: {$in: toDeleteIds}}, function(err, numRemoved) {
    exports.comments(req, res); //render a view        
}

1 Comment

you're right but I prefer doing single updates this time, just for learning purpose. Thank you.

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.