Don Rhummy
Don Rhummy

Reputation: 25830

When calling a function (in a loop) which uses a callback, how pass variables in and have them not change?

I'm calling sendMessage in a loop for a bunch of appIds that are in an array. When my callback is called, I need to check for an error and then "blacklist" the appId that has the error. The problem is that every way I've tried this causes the appId in the callback to be changed by the time it's called! So the wrong appId gets blacklisted.

I have three versions I tried (see below). One never blacklists, and the other two do the wrong one:

** THIS ONE BLACK LISTS THE WRONG ONE **

for ( var appName in apps )
{
    var app = apps[ appName ];

    var appId = app[ "appId" ];

    //Send message to that app
    chrome.runtime.sendMessage(
        app[ "appId" ],
        app,
        function (response)
        {
            var lastError = chrome.runtime.lastError;

            //Want to blacklist apps here
            if ( lastError && lastError.message.indexOf( "not exist" ) !== -1 )
            {
                //This blacklists the wrong one!
                myGlobalObject.addToTimeout( appId );
            }
        }
    );
}

** THIS ONE ALSO BLACK LISTS THE WRONG ONE **

for ( var appName in apps )
{
    var app = apps[ appName ];

    var appId = app[ "appId" ];

    //Send message to that app
    chrome.runtime.sendMessage(
        app[ "appId" ],
        app,
        function (response)
        {
            var lastError = chrome.runtime.lastError;

            //Want to blacklist apps here
            if ( lastError && lastError.message.indexOf( "not exist" ) !== -1 )
            {
                //This blacklists the wrong one!
                myGlobalObject.addToTimeout( this[ "appId" ] );
            }
        }.bind( app )
    );
}

** THIS ONE NEVER BLACK LISTS IT **

for ( var appName in apps )
{
    var app = apps[ appName ];

    //Send message to that app
    chrome.runtime.sendMessage(
        app[ "appId" ],
        app,
        function (response)
        {
            var lastError = chrome.runtime.lastError;

            //Want to blacklist apps here
            if ( lastError && lastError.message.indexOf( "not exist" ) !== -1 )
            {
                //Somehow this NEVER blacklists it!
                myGlobalObject.addToTimeout( app[ "appId" ] );
            }
        }
    );
}

Upvotes: 0

Views: 188

Answers (1)

jfriend00
jfriend00

Reputation: 707436

For callbacks that are called some time later, you need to "freeze" the value of the variable in a closure. There are several ways to do so, here's one using an immediate invoking function which passes the variables as arguments and they get captured in that function closure so their value remains what you want for the duration of the callback:

for ( var appName in apps )
{
    var app = apps[ appName ];

    var appId = app[ "appId" ];

    // this creates a closure which captures the values of your variables
    // and holds them at the desired value until the callback is called
    // a separate and unique closure will be created
    // for each cycle of the for loop
    (function(appID) {

        //Send message to that app
        chrome.runtime.sendMessage(
            appId,
            app,
            function (response)
            {
                // since this is called some time later
                // variables outside this may have different values
                // as the outer for loop continued to run
                var lastError = chrome.runtime.lastError;

                //Want to blacklist apps here
                if ( lastError && lastError.message.indexOf( "not exist" ) !== -1 )
                {
                    //This blacklists the wrong one!
                    myGlobalObject.addToTimeout( appId );
                }
            }
        );
    })(appID);
}

Upvotes: 3

Related Questions