views:

170

answers:

2

I'm trying to implement a chat client using JavaScript. The client is constructed using the following constructor:

function ChatClient(endpointUrl) {
    this.xmlHttp = createXmlHttpRequest(); 
    this.endpointUrl = endpointUrl;

    me = this;
    setInterval('me.receiveMessages()', FETCH_MESSAGES_INTERVAL);
}

function createXmlHttpRequest() {
    /* Create a new XMLHttpRequest object to talk to the Web server */
    var xmlHttp = false;
    /* @cc_on @ */
    /*
     * @if (@_jscript_version >= 5) try { xmlHttp = new
     * ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlHttp = new
     * ActiveXObject("Microsoft.XMLHTTP"); } catch (e2) { xmlHttp = false; } }
     * @end @
     */

    if (!xmlHttp && typeof XMLHttpRequest != 'undefined') {
     xmlHttp = new XMLHttpRequest();
    }

    return xmlHttp;
}

The chat client should be able to request messages from the server in a interval defined by the FETCH_MESSAGES_INTERVAL. Here's the code:

ChatClient.prototype.receiveMessages = function() {
    this.xmlHttp.open('GET', this.endpointUrl, true);
    this.xmlHttp.onreadystatechange = this.handleReceiveMessagesResponse();
    this.xmlHttp.send(null);
}

ChatClient.prototype.handleReceiveMessagesResponse = function() {
    console.log("readyState = " + this.xmlHttp.readyState);

    if (this.xmlHttp.readyState == 4) {
     var rawResponse = this.xmlHttp.responseText;
     document.getElementById('OutputArea').textContent = rawResponse;
    }
}

The problem is that when the handleReceiveMessagesReponse is called, the FireBug cosole shows that this.xmlHttp.readyState is always 1 (loading). FireBug also shows that my GET requests are receiving the expect responses from the server (status 200 with the string 'Hello' as body). Does anyone knows what's wrong wrong with this code?

+1  A: 

You calling the handleReceiveMessagesResponse method and assigning the return value (undefined) to the onreadystatechange property. I suspect you did not intend that and in fact should leave of the () at the end of that line. However that still won't work because the this context will not be what you expect it to be in the called function.

Try this:-

ChatClient.prototype.receiveMessages = function() {
  var self = this;
  this.xmlHttp.open('GET', this.endpointUrl, true);
  this.xmlHttp.onreadystatechange = handleReceiveMessagesResponse;
  this.xmlHttp.send(null);


  function handleReceiveMessagesResponse() {
    console.log("readyState = " + self.xmlHttp.readyState);

    if (self.xmlHttp.readyState == 4) {
      var rawResponse = self.xmlHttp.responseText;
      document.getElementById('OutputArea').textContent = rawResponse;
    }
  }  
}
AnthonyWJones
It worked, thanks! A final question from someone that's new to javascript and ajax: is it a common practice to implement a response handler as a closure, or there's a flaw in my chat design?
Alceu Costa
Common practice these days it to not attempt to build this sort of code yourself but use an existing framework like JQuery which simplifies this sort of thing immensely. Having said that I don't use JQuery having spent many years building up a in-house framework and yes such implementations do use a closures to good effect.
AnthonyWJones
A: 

This line:

this.xmlHttp.onreadystatechange = this.handleReceiveMessagesResponse();

Should be:

this.xmlHttp.onreadystatechange = (function( fn ) {
  return function() {
    fn.handleReceiveMessagesResponse.apply( fn, arguments );
  };
})( this );

Off the top of my head. The reasoning behind this madness:

  • onreadystatechange only accepts Function values. Calling the function will return its respective return value.
  • Because you want to retain reference to this, we have to encapsulate it by either assigning it to a variable, or encapsulating it inside a closure function, the latter being what I have done.
  • We can then change the context of the 'this' variable for the handleReceiveMessagesResponse method by using apply / call. I also pass in the arguments just in case...
Tim