4

Possible Duplicate:
Javascript OOP return value from function

I have a class defined like this

function SocialMiner(tabUrl) 
{
    var verbose=true;

    var profileArray=new Array();

    this.tabUrl=tabUrl;

    this.getTabUrl=function(callback)
    {
        chrome.tabs.getSelected(null, function(tab)
        {


            callback(tab.url);
        });
    }   

    this.setTabUrlValue=function(pageUrl)
    {
        this.tabUrl=pageUrl;
        console.log("22"+this.tabUrl);   //this statement shows url correctly
    }
}

When I call this method like these

 miner.getTabUrl(miner.setTabUrlValue);

   miner.logToConsole("1"+miner.tabUrl);   //This statement returns undefined

The console.log inside callback correctly outputs url , however, the tabUrl property of miner ojbect is undefined , as seen in second console.log. Why is it so ?

4
  • does the logToConsole method exists ? Commented Jun 2, 2011 at 23:18
  • so there have been 3 proper answers to this question, the problem is a misunderstanding of what "this" is. Why you haven't been able to get a working answer is your code snippet is highly dependant on the rest of your script. I would highly recommend going to jsfiddle.net, and getting a more generic answer together to illustrate your problem. This will make it easier to both understand your question, and give you a proper answer. Commented Jun 2, 2011 at 23:54
  • @Matt : see the corrected code, Its still not working. miner.TabUrl comes undefined in second line. gist.github.com/1005576 Commented Jun 2, 2011 at 23:55
  • This is not a duplicate, the post in your reply contains a different question Commented Jun 3, 2011 at 1:09

3 Answers 3

2

The solution is to save a reference to this within the constructor (available later on via closure):

var that = this; //in the top of the SocialMiner constructor function

and in setTabUrlValue use:

that.tabUrl=pageUrl;

I suspect running a method as a function (callback) loses scope, i.e. doesn't know of any this anymore. In other words, it runs within the scope of the constructor, not as a method of the instance using it. A variable referencing this in the constructor scope is available to the function, and that points to the right this on instance creation.

You could also force callback to run in the current instance scope like this:

callback.call(this,tab.url);

In that case you can leave this.tabUrl=pageUrl; as it is.

This is an simplification of your code. The methods return this to be able to directly reference a property of the instance (see console.log last line):

function Some(){
     var that = this; // note: not used in this example
     this.getA = function(callback){
         someval = 'foobar';
         callback.call(this,someval);
         return this;
     };
     this.getB = function(val){
         this.val = val;
         return this;
     };
}
var some = new Some;
console.log( some.getA(some.getB).val ); //=> foobar

Taking a look @ your code again, I think you're loosing scope twice, because callback is called from within another callback. That's why I think your code on that spot should be:

chrome.tabs.getSelected(
      null, 
      function(tab) {
        callback.call(that,tab.url); //< use that here
      }
);

Furthermore, in you code @ github, I don't see any instantiation of the miner instance.

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

5 Comments

Can you explain more, I tried this but didn't work. See the gist here:gist.github.com/1005576
Hi Madhur Ahuja, see the edited answer. Maybe I'll look later, but for now it's late here and time to get some sleep ;~)
Have a good night, but still no luck :(
Thanks. I don't see .logToConsole defined in your constructor, may that be the problem?
@Kooilnc: Check out the full code here gist.github.com/1005576
1

this is a tricky beast in JavaScript and as others have pointed out is the key to the issue. The problem with using this everywhere is that it's value can change depending on who/where the function is called from (for example, see the call and apply methods in JavaScript). I'm guessing that if you wrote the value of this to the console in the the callback from the chrome.tabs.getSelected function you'd find it isn't your miner any more.

The solution is to capture a reference to the this that you're actually interested in when you know for sure it's the right one & then use that reference from then on. Might make more sense to see it commented in-line in your example:

function SocialMiner(tabUrl) 
{
    //At this point we know "this" is our miner object, so let's store a
    //reference to it in some other (not so transient) variable...
    var that = this;

    var verbose=true;

    var profileArray=new Array();

    this.tabUrl=tabUrl;

    this.getTabUrl=function(callback)
    {
        chrome.tabs.getSelected(null, function(tab)
        {
            //at this point "this" is whatever the "chrome.tabs.getSelected"
            //method has decided it is (probably a reference to the tab or something)
            callback(tab.url);
        });
    }   

    this.setTabUrlValue=function(pageUrl)
    {
        //because this can be called from anywhere, including the chrome callback
        //above, who knows what "this" refers to here (but "that" is definitely
        //still your miner)
        that.tabUrl=pageUrl;
        console.log("22"+that.tabUrl);
    }
}

You can see how much this shifts around in libraries that use callbacks heavily like jQuery, where often this is set to convenient values, but certainly not the same this that was logically in scope when you made the initial call.

EDIT: Looking at the full source (& example) you posted, this is just a timing issue where obviously the chrome.tabs.getSelected is returning asynchronously after your "second" call to log goes through...

console.log("5");
miner.getTabUrl(miner.setTabUrlValue);   //setTabUrlValue is logging with '22'
console.log("6");
miner.logToConsole("1"+miner.tabUrl);
console.log("7");


// Output:
5
6
1 undefined       //the chrome.tabs.getSelected hasn't returned yet...
7
22 http://url     //now it has (so if you tried to use miner.tabUrl now you'd be all good...

The solution is to put all the stuff after the get/set into the callback, since you don't want anything happening until after that tabUrl is finished being set... so something like this:

console.log("5");
miner.getTabUrl(function(pageUrl) {
    miner.setTabUrlValue(pageUrl);
    console.log("6");
    miner.logToConsole("1"+miner.tabUrl);
    console.log("7");
});

Hopefully that will see you getting your results in the order you expect them.

1 Comment

Thanks for the detailed explanation. I am using this implementation now as seen in gist. gist.github.com/1005576 However, second line is still printing undefined, for miner.tabUrl
1

I think this happens because closure vars do not survive a function call.

3 Comments

Can you elaborate, I think its because call back is executed asynch. Isn't it ?
The call to callback() doesn't preserve the object that's being operated on. That is a static function call. What you would have to do is apply the callback to the object as a method: callback.call(this, pageUrl);
@Zath: The above solution doesn't seem to work

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.