Search code examples
javascriptajaxxmlhttprequestthissetinterval

Context problems with XMLHttpRequest.onreadystatechange


Background

I am making a request every 5 seconds using XMLHttpRequest and I want to print my name when I receive the response.

To do this I am using onreadystatechange which allows me to define a callback function when I receive the answer.

Problem

To achieve this, I am using a class. When I first initiate the class I say my name immediately, and then I start a process using setTimeInterval to make a request every 5 seconds and see my name.

The problem is that I see my name the first time, but then I never see it again. The issue is that this seems to be in different context, and thus this.sayMyName() doesn't exist because it doesn't belong to the xhr object.

What I tried

To fix this I tried using scoping by following another StackOverflow question but unfortunately this remains undefined.

Code

class Cook {

    constructor() {
        // innitial call so we don't have to wait 
        //5 seconds until we see my name
        this.getCookInfo(); 

        setInterval(this.getCookInfo, 5000);
    }

    getCookInfo() {

        var xhr = new XMLHttpRequest(),
            method = "GET",
            url = "https://best.cooks.in.the.world.org/";

        xhr.open(method, url, true);

        //Call a function when the state changes.    
        xhr.onreadystatechange = (self => {
            return () => {
                if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200)
                    self.sayMyName();
            };
        })(this);
    }

    sayMyName() {
        console.log("Heisenberg");
    }
}

Questions:

  1. Is there a way to fix this code without have to pass a context object to the setInterval function?

Note Kudos++ for those of you who get my reference :P


Solution

  • bind the this.getCookInfo function to this

    then you can rally simplify your code

    class Cook {
    
        constructor() {
            // innitial call so we don't have to wait 
            //5 seconds until we see my name
            this.getCookInfo(); 
    
            setInterval(this.getCookInfo.bind(this), 5000);
        }
    
        getCookInfo() {
    
            var xhr = new XMLHttpRequest(),
                method = "GET",
                url = "https://best.cooks.in.the.world.org/";
    
            xhr.open(method, url, true);
    
            //Call a function when the state changes.    
            // no need for self gymnastics any more
            // using onload, no need to test reasyState either
            xhr.onload = e => {
                if (xhr.status == 200)
                    this.sayMyName();
            };
            // your code lacks one thing
            xhr.send();
        }
    
        sayMyName() {
            console.log("Heisenberg");
        }
    }
    

    An alternative -

    class Cook {
    
        constructor() {
            this.getCookInfo(); 
        }
    
        getCookInfo() {
            var getit = () => {
                var xhr = new XMLHttpRequest(),
                    method = "GET",
                    url = "https://best.cooks.in.the.world.org/";
    
                xhr.open(method, url, true);
    
                //Call a function when the state changes.    
                xhr.onload = e => {
                    if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200)
                        this.sayMyName();
                };
                xhr.send();
            };
            getit();
            setInterval(getit, 5000);
        }
    
        sayMyName() {
            console.log("Heisenberg");
        }
    }
    

    I'm only 99% sure this is right though :p