6

I have this callback function setup:

var contextMenu = [];
var context = [ { "name": "name1", "url": "url1" }, {"name": name2", "url: "url2" } ];
for(var i=0; i < context.length; i++) {
    var c = context[i];
    var arr = {};
    arr[c.name] = function() { callback(c.url); }
    contextMenu.push( arr );
}
function callback(url) {
   alert(url);
}

The problem is that the url value passed to the callback is always the last value in the context variable - in this case "url2". I am expecting to pass specific values to each "instance" of the callback, but as the callback seems to be remember the same value, the last time it was referred.

I am kind of stuck. Any help would be appreciated.

PS: I am using jQuery ContextMenu which, to my understanding, does not support sending custom data to its callback functions. It is in this context that I have this problem. Any suggestions to overcome in this environment is also helpful!

1
  • Possible duplicate of this post and dozens of others Commented Feb 28, 2011 at 16:11

3 Answers 3

16

Use an additional closure.

arr[c.name] = (function(url) { 
    return function() { callback(url); }
})(c.url);

See Creating closures in loops: A common mistake and most other questions on this topic, and now your question is also added to this pool.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! I was mystified by "closures" many times and now it is getting more cleared with some hands-on experience!
Closures can be awesome, and especially since functions are first-class objects in JavaScript, its kind of hard to do without closures. Imagine passing every single variable being referenced in the function as an argument - in this case both callback, and url and all the headaches of whether the argument was passed by value or reference.
I think it's worth noting that it's not the closure per-se that makes it work, but more specifically that this closure code includes passing an argument - which makes for a passing-by-value in place of the reference kept in the original closure, thus overcoming the original problem. I think this makes the explanation more complete.
3

You are creating a series of closure functions inside the for loop

arr[c.name] = function() { callback(c.url); }

and they all share the same scope, and hence the same c object which will point to the last element in your array after the loop finishes.

To overcome this issue, try doing this:

arr[c.name] = function(url) {
    return function() { callback(url); };
}(c.url);

Read more about closures here: http://jibbering.com/faq/notes/closures/

Comments

0

General solution

Callback creator helper

I created a general callback creator along the Creating closures in loops: A common mistake that Anurag pointed out in his answer.

Parameters of the callback creator

  • The function's first parameter is the callback.
  • Every other parameter will be passed to this callback as parameters.

Parameters of the passed callback

  • First part of the parameters come from the arguments you passed to the callback creator helper (after the first parameter as I described previously).
  • Second part comes from the arguments that will be directly passed to the callback by its caller.

Source code

//Creates an anonymus function that will call the first parameter of
//this callbackCreator function (the passed callback)
//whose arguments will be this callbackCreator function's remaining parameters
//followed by the arguments passed to the anonymus function
//(the returned callback).
function callbackCreator() {
    var functionToCall = arguments[0];
    var argumentsOfFunctionToCall = Array.prototype.slice.apply(arguments, [1]);
    return function () {
        var argumentsOfCallback = Array.prototype.slice.apply(arguments, [0]);
        functionToCall.apply(this, argumentsOfFunctionToCall.concat(argumentsOfCallback));
    }
}

Example usage

Here is a custom AJAX configuration object whose success callback uses my callback creator helper. With the response text the callback updates the first cell of a row in a DataTables table based on which row the action happened, and prints a message.

{
    url: 'example.com/data/' + elementId + '/generate-id',
    method: 'POST',
    successHandler: callbackCreator(function (row, message, response) {//Callback parameters: Values we want to pass followed with the arguments passed through successHandler.
            table.cell(row, 0).data(JSON.parse(response).text);
            console.log(message);
        },
        $(this).parents('tr'),//Row value we want to pass for the callback.
        actionName + ' was successful'//Message value we want to pass for the callback.
    )
}

Or in your case:

arr[c.name] = callbackCreator(function(url) {
        callback(url);
    },
    c.url
);

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.