18

This is my code:

request_xml: function()
        {
        http_request = false;
                    http_request = new XMLHttpRequest();
                     if (http_request.overrideMimeType) 
                            {
                            http_request.overrideMimeType('text/xml');
                            }
                          if (!http_request)
                          {
                                return false;
                          }
                        http_request.onreadystatechange = this.response_xml;
                        http_request.open('GET', realXmlUrl, true);
                        http_request.send(null);
                        xmlDoc = http_request.responseXML;
                        
},



response_xml:function ()
    {
        if (http_request.readyState == 4)
        {
            if(http_request.status == 404 && countXmlUrl<=3)
            {
                countXmlUrl++;
                
                realXmlUrl = xmlUrl[countXmlUrl];
                this.request_xml();
            }
            if (http_request.status == 200)
            {
                xmlDoc = http_request.responseXML;
                alert("need to update3");
                this.peter_save_data();
            }
            
        }
    },

peter_save_data:function()
    {
// removed function code
},

Strangely, the alert fires without a problem but the function call underneath gives me this error:

Error: this.peter_save_data is not a function

Calling the same function from another function elsewhere works fine.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Ryan
  • 9,821
  • 22
  • 66
  • 101
  • 4
    I would imagine it's because you're using `this` in the wrong scope. Have you tried passing it to the `peter_save_data()` function but calling it like `objWhatever.peter_save_data(this)`? – Jared Farrish Jun 11 '11 at 18:49
  • That's not the full code. One important piece of the puzzle that is missing is *how* `response_xml` is being called -- this is important, because it will change what `this` is (see Jared's comment). Remember that `this` can be thought of as "the receiver of the method call". –  Jun 11 '11 at 18:52
  • Why not call it with its qualified name? – Anirudh Ramanathan Jun 11 '11 at 18:52
  • 1
    @anirudh4444 Imagine the above code comes from a `prototype` declaration. Thus this is the most general form that can be used. –  Jun 11 '11 at 18:53
  • Ok, edited the post for clarity... I have no problems pasting the full code, but its a large file... – Ryan Jun 11 '11 at 18:57
  • Does this answer your question? [JavaScript error: "is not a function"](https://stackoverflow.com/questions/9825071/javascript-error-is-not-a-function) – Liam Apr 05 '22 at 13:34

3 Answers3

31

You could do this, right before you call the XML generation.

var that = this;

and later...

that.peter_save_data();

Because this frequently changes when changing scope by using a new function, you can't access the original value by using it. Aliasing it to that allows you still to access the original value of this.

Anirudh Ramanathan
  • 46,179
  • 22
  • 132
  • 191
  • 2
    I would consider that a potentially unnecessary global variable, which could be difficult to debug (for instance, if it is already used by something else or set elsewhere and not cleared out when the method is complete). – Jared Farrish Jun 11 '11 at 18:56
  • 1
    That variable does not need to be global. It needs to be declared locally, only before the context changes. – Anirudh Ramanathan Jun 11 '11 at 19:01
  • Thanks so much this fix my problem! – Cloud Apr 05 '19 at 11:55
6

One important piece of the puzzle that is missing is how response_xml is being called. This is important, because it will change what this is (see Jared's comment).

Remember that this can be thought of as (roughly) "the receiver of the method call". If response_xml is passed directly to use as a callback then of course it won't work -- this will likely be window.

Consider these:

var x = {f: function () { return this }}
var g = x.f
x.f() === x    // true
g() === x      // false
g() === window // true

Happy coding.


The "fix" is likely just to change how response_xml is being called. There are numerous ways to do this (generally with a closure).

Examples:

// Use a closure to keep he object upon which to explicitly invoke the method
// inside response_xml "this" will be "that",
// which was "this" of the current scope
http_request.onreadystatechange = (function (that) {
   return function () { return that.response_xml() }
}(this)

// Or, alternatively,
// capture the current "this" as a closed-over variable...
// (assumes this is in a function: var in global context does not create a lexical)
var self = this
http_request.onreadystatechange = function () {
   // ...and invoke the method upon it
   return self.response_xml()
}

Personally, I would just use jQuery or similar ;-)

  • 1
    @Ryan Updated answer for a fix ;-) –  Jun 11 '11 at 18:58
  • I voted up your comment, but for a newb like me its a bit hard to understand. Although both solutions are working i am using the below one (simpler in my head) so i have accepted that one as the answer. Hope you dont mind, wish i could pick two answers here, and thanks! – Ryan Jun 11 '11 at 19:05
  • 1
    @Ryan It's okay :-) Hopefully my examples were a bit helpful and your problem is solved. The important thing is to keep in mind that methods in JavaScript are *not bound* to an object -- they are just normal functions! The `this` is only correctly propagated through with how they are invoked. (It can be altered with `call` or `apply` or changing the "receiver" as shown). –  Jun 11 '11 at 19:07
-1

If you want a class-like behavior, use the right syntax, The libraries that use that, are using JSON to pass a parameter to a function that makes a class out of it.

function MyClass(CTOR paarams){
    var response_xml=function ()
    {
        if (http_request.readyState == 4)
        {
            if(http_request.status == 404 && countXmlUrl<=3)
            {
                countXmlUrl++;

                realXmlUrl = xmlUrl[countXmlUrl];
                this.request_xml();
            }
            if (http_request.status == 200)
            {
                xmlDoc = http_request.responseXML;
                alert("need to update3");
                this.peter_save_data();
            }

        }
    }

    var peter_save_data=function()
    {
       // removed function code
    }
}

var Test = new MyClass(somthing,another_something);
Test.response_xml();
//etc etc.

Or, use the libraries like Mootools where you can do it as JSON:

var T = new Class({
    response_xml:function ()
    {
        if (http_request.readyState == 4)
        {
            if(http_request.status == 404 && countXmlUrl<=3)
            {
                countXmlUrl++;

                realXmlUrl = xmlUrl[countXmlUrl];
                this.request_xml();
            }
            if (http_request.status == 200)
            {
                xmlDoc = http_request.responseXML;
                alert("need to update3");
                this.peter_save_data();
            }

        }
    },

    peter_save_data:function()
    {
      // removed function code
    }

});
var X = new T();//etc etc
Itay Moav -Malimovka
  • 52,579
  • 61
  • 190
  • 278