XMLHTTP notes: readyState and the events

As we all know an xmlhttp script requires the use of the readystatechange event. In theory, using the load event is also possible, but Explorer doesn't support it on xmlhttp requests.

Both these events, and the readyState property, have a few odd quirks when used in an xmlhttp environment, though. These quirks don't impact standard xmlhttp scripts too much, but as soon as you want to use the event objects or readyStates other than 4 you need to know about them.

readyState

The readystatechange event fires when the readyState of the request changes. This property can have four values, which theoretically work in the way shown below. In practice, though, the browsers don't quite follow this de facto standard.

0 Uninitialized - open() has not been called yet.
1 Loading - send() has not been called yet.
2 Loaded - send() has been called, headers and status are available.
3 Interactive - Downloading, responseText holds the partial data.
4 Completed - Finished with all operations.

readyState 4 is equivalent to the load event and is a standard part of any xmlhttp script. I have never understood what the other three readyStates are good for; why would you need to know that responseText holds the partial data, for instance? One more mystery to solve.

That said, let's try the following bit of script. You'd expect four alerts, 1, 2, 3 and 4.

xmlhttp.open("GET","somepage.xml",true);
xmlhttp.onreadystatechange = checkData;
xmlhttp.send(null);

function checkData()
{
	alert(xmlhttp.readyState);
}

Results:

Where have the missing readyStates gone in Safari and Opera? I have no idea.

Let's move the event handler assignment to a different point in the code:

xmlhttp.onreadystatechange = checkData;
xmlhttp.open("GET","somepage.xml",true);
xmlhttp.send(null);

Results:

All browsers now add an extra readyState of 1 at the start of their sequence. Explorer and Mozilla give a "1" alert twice, and that's odd, because the event name is clearly readystatechange and a change from 1 to 1 is no change. Opera still lacks the readyState 2. Odd, odd.

Let's move the event handler assignment again:

xmlhttp.open("GET","somepage.xml",true);
xmlhttp.send(null);
xmlhttp.onreadystatechange = checkData;

Explorer doesn't react at all if you assign the readystatechange event after the send() method has been called.

In conclusion, no browser correctly supports readyState in all cases. Fortunately the all-important value of 4 is unaffected by this odd series of bugs.

readystatechange

Let's consider the events themselves:

xmlhttp.open("GET","somepage.xml",true);
xmlhttp.onreadystatechange = checkData;
xmlhttp.send(null);

function checkData(e)
{
	var evt = e || window.event;
	var rs = xmlhttp.readyState || "None";
	alert(evt.type + ' ' + rs);
}

Now you'd expect to get an alert "readystatechange 1" through 4. Unfortunately only Safari gives this correct response, even though it starts at readyState 2, as we saw above.

The readystatechange event object is entirely absent in Mozilla and Opera, while Explorer feels its type is a load event.

Another related point:

When you use synchronous loading in Mozilla (ie. browser waits until the XML file has been loaded before doing anything else), the readystatechange event is not available.

This is not a huge problem, because the idea behind synchronous loading is that you wait for the XML file to be available, anyway. The line after the xmlhttp.send(null) is executed only when the XML is there. Nonetheless this point should be noted, too.

load

Explorer has a point in so far as the load event can be seen as a subset of the readystatechange event. load fires when the page has been loaded completely, which is equivalent to saying the readyState is 4: completed.

Let's try it:

xmlhttp.open("GET","somepage.xml",true);
xmlhttp.onload = checkData;
xmlhttp.send(null);

function checkData(e)
{
	var evt = e || window.event;
	var rs = xmlhttp.readyState || "None";
	alert(evt.type + ' ' + rs);
}

So Explorer doesn't support the load event on xmlhttprequests. We already knew this, but we should realize it doesn't even allow you to set the event handler, since it allows only a very limited set of properties on the xmlhttp object.

The event object is still missing in Opera, though not in Mozilla.

This is the blog of Peter-Paul Koch, mobile platform strategist, consultant, and trainer. You can also follow him on Twitter.
Atom RSS

I’m speaking at the following conferences:

(Data from Lanyrd)

Categories:

Monthlies:

Comments

Comments are closed.

1 Posted by Analgesia on 21 September 2005 | Permalink

shouldn't
function checkData()
be
function checkData(e)

otherwise e is never defined. I find it strange the code still works, but I'm not that into JS

2 Posted by Ma￱ungo on 21 September 2005 | Permalink

See: http://www.whatwg.org/specs/web-apps/current-work/#readystate
The handler must be set before open state to alert '1'

Did you try the second example in synch way ?
xmlhttp.onreadystatechange = checkData;
xmlhttp.open("GET","somepage.php?"+Math.random(), false );
xmlhttp.send(null);

Explorer: 1-1-2-3-4
Firefox: (nothing)
Safari: (no tested)
Opera: 1-2-3-4

3 Posted by ppk on 21 September 2005 | Permalink

Yes, it should be checkData(e). Corrected.

4 Posted by brett on 22 September 2005 | Permalink

Consider if the page you're getting via xmlhttp takes a long time; this allows you to show a 'loading' message while the user waits. It may also change your results - perhaps the page you're getting returns instantly.

5 Posted by Alex Lein on 22 September 2005 | Permalink

Also consider the xmlhttp.status also varies from browser to browser.

(xmlhttp.status==404) // file not found
(xmlhttp.status==200) // file found and loaded
(xmlhttp.status==304) // file found, but determined unchanged and loaded from cache

Opera 8.x really loves that last status, I haven't noticed it with Mozilla or IE6 yet. I don't have a Mac to test Safari.

6 Posted by tsuyoshi on 23 September 2005 | Permalink

Thank you for intriguing experiments. It's interesting that each browser you tested behaves differently in some ways....

However, I cannot agree with you in two points. To me, the results do not look as odd as you say.

First, you say that no browser correctly supports readyState in all cases. But according to your results, Safari correctly supports readyState and readystatechange, doesn't it? If readyState==0 means open() is not yet called, then in the first example, the handler is set up after readyState becomes 1, and therefore it should not be called with readyState==1, as you say after the second example.

Second, the behavior of IE in the third example is reasonable if the remote access takes such a short time that it is completed before you set the handler. As brett says in the comment #4, the result here may change if the remote access takes a longer time.

7 Posted by ppk on 24 September 2005 | Permalink

You mean the readyState has already been changed from 0 to 1 when the event handler is set?

Yes, that seems to be quite possible, and in that case Safari would be the only browser to support readyState correctly.

8 Posted by tsuyoshi on 25 September 2005 | Permalink

Yes. To be honest, I cannot follow why you expect four alerts in the first example, instead of three. You write: "0 Uninitialized - open() has not been called yet." Doesn't this mean open() changes readyState from 0 to 1?

9 Posted by David Flanagan on 30 September 2005 | Permalink

Somewhere along the line, I got the idea that the browser would call onreadystatechange multiple times with readyState==3 when downloading a long document. This allows you to show a "loading..." animation to the user, for example, or to track the progress of the download without polling responseText.

I don't know where I got this idea; perhaps I made it up myself :-) Have you tested for it?

But if readyState 3 is only called once, it is pretty useless: it simply tells us that the request is in an indeterminate state: headers may or may not be available, some content may or may not be in responseText.

On a related note, WHATWG is attempting to standardize all this. Their current spec say sthat you can call getResponseHeader() in readyState 3, and if the header has been received, it will be returned to you.

10 Posted by David Flanagan on 1 October 2005 | Permalink

Okay, I've confirmed it. Firefox calls onreadystatechange multiple times for state 3 when doing a long download. This is a good thing. But IE doesn't do it. And the WHATWG spec, as it currently stands, will forbid the Firefox behavior.

11 Posted by ppk on 3 October 2005 | Permalink

Very interesting. Do you happen to have a link to a test script available?

Officially WHAT is correct in my opinion: the ready state doesn't change from 3 to 3.

Nonetheless Mozilla's behaviour could be interesting IF there is a way to see how much of the document has been loaded.

12 Posted by James on 5 October 2005 | Permalink

After fiddling around for quite some time with the XMLHttp functionality, I thought it might be interesting to write a navigation based on it.

It's viewable here; http://www.bandit.co.nz/bandit/me/

It's still very much under construction, but I thought it may be an interesting example.

The biggest issue I came across was the fact that the XMLHttp request doesn't seem to fully load the content of the page for a few milliseconds - and I couldn't get the page to work correctly without a setTimeout before the content change.

13 Posted by Chris Bloom on 7 October 2005 | Permalink

For the life of me I can't get the state to change in Firefox using the following:

this.req.onreadystatechange = this.processReqChange;
this.req.open("POST", url, false);
this.req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
this.req.send(data);

It fires once when onreadystatechange is set, then nothing. Does this not work with the POST method?

14 Posted by ppk on 7 October 2005 | Permalink

You use synchronous loading (last argument of open() is false), so readystatechange doesn't work in Mozilla. Change "false" to "true".

15 Posted by Chris Seeling on 10 October 2005 | Permalink

To allow multiple requests I tried
var req = new XMLHttpRequest;
req.onreadystatechange = function () {processReqChange(req);};

function processReqChange(req) {...}

Seems to work for both Mozilla and IE6, but will using a function local variable reference cause potential problems (memory leaks, garbage collection claiming referenced memory etc..)?

16 Posted by Zzen on 11 October 2005 | Permalink

I second David [#9,#10] in having a need for monitoring the progress of the download. I just had my XmlHttpRequest load several MEGABYTES of data before determining I'm receiving an endless loop (from a 3rd party website).

Fortunatelly, I was able to cut these transfers in the begining by checking for Content-Length HTTP header - but this might not always be the option (i.e. when the data is transfered as chunked and the Content-Length is not specified).

But I have to acknowledge that ppk (and WhatWG) is right, that onreadystateCHANGE should not fire between states 3 and 3.

I would propose adding a XmlHttpRequest.received readonly integer attribute to the WhatWG Web Apps 1.0 draft. I'd leave the repetitive firing for user-land (i.e. a setInterval() created upon reaching readyState 3).

Now might be a good time for the proposal, since the draft is under heavy development (ppk - hint, hint).

http://www.whatwg.org/specs/web-apps/current-work/#scripted-http

17 Posted by Ric on 12 October 2005 | Permalink

It looks like this problem is with Internet Explorer and not just
XmlHttp object
I put an example up on http://www.ECMASCRIPT.net/
If you click on the link in Firefox you get the document as it streams.
If you click on the link in IE, it waits until the whole document is
done the first time. If you hit refresh on /TimedOutput.aspx you get
the same as firefox.


The button simply uses the Xmlhttp object to request the same page and
display the result as it get it. It works well in fireFox, but not IE

18 Posted by Doeke Zanstgra on 20 October 2005 | Permalink

For what it's worth, one comment on "correct". The XmlHttpRequest object is a de facto standard, thought up by Microsoft (sometimes they do something good). Mozilla copied it, and Opera and Safari followed. De facto can be read as: the implementation is the specification. In this sense only the IE implementation is "correct".

[DISCLAIMER: I'm not saying the above is the only way to view it; even a single person is allowed to have more than one perspective. So please: read this just for your info]

One request. Please, don't use the XmlHttpObject in a synchronous way. It will freeze your web page. Even if you are loading a tiny document; there is always a moment when the server has a hickup and it will take seconds before the document is served. This is very annoying for the user. Besides, making an asynchronous call is not difficult...

19 Posted by Doeke Zanstgra on 20 October 2005 | Permalink

I just realized what I was trying to explain with my "correct" story. The readystatechange handler is used internally on socket level. Most readyStates values have no meaning for the client-side programmer, but are necessary for the socket programmer. At socket-level you need to know when the first headers have arrived. For example: you might want to protect your connection with a watchdog timer.

The creator of the XmlHttpObject was so kind to expose these values to javascript, so we now can create an on-content-ready event (when the object was introduced, probably nobody has thought of this). And to make things more robust, you can implement your own connection timeout with these different states.

20 Posted by Soloman Rikk on 19 August 2006 | Permalink

Did you try the second example in synch way ?
xmlhttp.onreadystatechange = checkData;
xmlhttp.open("GET","somepage.php?"+Math.random(), false );
xmlhttp.send(null);

Explorer: 1-1-2-3-4
Firefox: (nothing)
Safari: (no tested)
Opera: 1-2-3-4

21 Posted by Jean Marie on 21 August 2006 | Permalink

1000 Thanks for your explanation. It helped me to solve a problem with Firefox and syncronous XMLHttp requests.

Keep it up!

Best regards
Jean Marie