XMLHTTP notes: abort() and Mozilla bug

In my continuing quest to understand XMLHTTP I gathered some very intriguing material that I'm quite sure will save somebody else's ass. Today I offer a closer look at the abort() method, as well as an as yet unexplained bug in Mozilla which causes the responseXML to go missing.

A note first of all: I tested everything below in Safari, too, and it hasn't given me a single spot of trouble. It does exactly what I expect it to do, when I expect it. Kudos to the Safari team!

The purpose

I'm currently working on a very simple XMLHTTP script (I hardly dare call it an application) for a Dutch ISP. When a user enters his postal code, an XMLHTTP request is automatically fired to retrieve data about the download speeds the user can expect when signing up for the ISP's new fast ADSL connection.

Update: This project is now online.

Dutch postal codes look as follows: 1072 VP. Four numbers, an optional space, two letters. That doesn't really matter, but anyway.

In itself the XMLHTTP component is very simple: send postal code and house number to server, and retrieve expected download speed and/or an error message. Show these bits of data in page, start up some nice animations, ready.

The tricky bit

The tricky bit is that the client definitely wants the XMLHTTP to start working as soon as the user has entered the second number. The check for this partial postal code is still very rough, and the server side programmer sensibly opted to send back the slowest download speed associated with it. When the user enters more of his postal code and/or his house number, the search is refined.

So far so good. The keyup event fires the xmlhttp request. (Not keypress! It fires before the new character has been added to the input field.)

The problem, obviously, is that a user may type so fast, and/or the server may be so slow, that one request overtakes another. User types second number, request goes out, but before it comes back he types the third number, and another request goes out etc. This turns out not to be quite such a problem as I expected, but the writing of this part of the script uncovered quite a few interesting features.

abort()

From the outset I decided to use only one xmlhttp object. When the user fires a new request, the old one is (should be) overwritten. Only the data retrieved by the last request counts, the results of earlier requests can be safely discarded.

It's here that the abort() method comes in. My original plan was to check if the single xmlhttp object was busy downloading something before sending a new request. If it was, I could abort the previous request and send my new one. This plan looked good in theory, and it even sort of works in practice, but while implementing this bit of the script I discovered quite a few things.

Necessary?

First of all: is it necessary to use abort()? Not really, except in Mozilla.

If you take an xmlhttp object that's busy sending and receiving and tell it to send another request, it simply stops doing whatever it does and sends out the new request. Except in Mozilla.

Error: uncaught exception: [Exception... "Component returned failure code: 0xc1f30001
(NS_ERROR_NOT_INITIALIZED) [nsIXMLHttpRequest.send]"  nsresult: "0xc1f30001 (NS_ERROR_NOT_INITIALIZED)"
location: "JS frame :: [URL censored] :: zendGegevens :: line 68"  data: no]

Since line 68 is the line that does xmlhttp.send(null) I assume that Mozilla cannot handle the assignment of a new request to an xmlhttp object that's still busy. (I'm guessing. I cannot actually read these confused error messages. It's a safe guess, though.)

So far so bad. abort() is necessary for Mozilla.

Custom properties on an xmlhttp object

So I added an if clause that sees if the xmlhttp object is still busy. Originally I tried to set a custom property to the xmlhttp object: xmlhttp.isBusy = true when I send the request, and xmlhttp.isBusy = false when the request is ready. Didn't work.

Explorer doesn't allow you to set custom properties on an xmlhttp object.

No problem, I created a global variable isBusy, which worked fine.

Clear readystatechange!

So I wrote this bit of script:

var queryString = [gather data];
if (isBusy)
{
	xmlhttp.abort();
}
xmlhttp.open("GET",url+queryString,true);
isBusy = true;
xmlhttp.onreadystatechange = catchData;
xmlhttp.send(null);

function catchData()
{
	if (xmlhttp.readyState != 4) return;
	isBusy = false;
	[handle data]
}

The results were quite odd, and it took me a long time to track down the cause:

When the abort() method is used, the readystatechange event fires in Explorer and Mozilla. Worse, readyState = 4, which means that the average xmlhttp script assumes the data has been loaded correctly. This can give very weird effects.

The solution was to clear the onreadystatechange event handler before calling abort().

if (isBusy)
{
	xmlhttp.onreadystatechange = null; // no go in IE
	xmlhttp.abort();
}

However, setting onreadystate = null in the context of abort() gives a very weird error in Explorer.

The error reads something like "Types don't match"; I'm translating from Dutch here. Therefore in the end I decided to assign an empty function to the event handler:

if (isBusy)
{
	xmlhttp.onreadystatechange = function () {}
	xmlhttp.abort();
}

Don't ask me why this is necessary, but it works.

Mozilla bug: no responseXML

Then the final, as yet unsolved Mozilla bug creeped up.

Sometimes, even though the xmlhttp request goes fine, there's just no responseXML. Though this can happen with "intermediate" requests, which are overruled by a following request, it can also happen with the very last request that is fired, one that is not overruled and should work fine.

I confirmed this bug in the new Firefox 1.5 (Beer Park).

Two points merit further attention:

  1. The responseXML goes missing only when the xmlhttp request skips readyState 3 (alternately: readyState 3 doesn't fire when the responseXML is missing). In all cases 1, 2 and 4 fire normally.
  2. The bug definitely has something to do with abort(), because when I remove the abort() bit the bug disappears. Of course, then the xmlhttp.send() bug I mentioned earlier returns in all its glory. Remember: this bug sometimes occurs with the last, non-aborted xmlhttp request, so it can't be simply a result of aborting the request.

That's where I stand now. I have no idea what causes the bug, or how to solve it. In the current situation I have the choice between two bugs. The script won't ever work correctly in Mozilla when the user types very fast and/or the server is very slow.

Note: meaning of the five ready states

BTW: while doing research I finally found a clear description of the five ready states at the new developer.mozilla.org. It says:

Value Description
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.
ppknote: Can go missing in Mozilla, in which case the responseXML also goes missing.
4 COMPLETED - Finished with all operations.

Although I doubt whether this is an entirely correct description for all browsers, it's certainly much better than the obscure ones we find elsewhere. Until now I only found descriptions that pompously state "Uninitialized, Loading, Loaded, Interactive, Completed" and leave us unenlightened as to what that actually means.

来源:http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_a_1.html

加支付宝好友偷能量挖...


评论(0)网络
阅读(182)喜欢(0)JavaScript/Ajax开发技巧