Weird XMLHttpRequest error in IE – just one call allowed

Recently in a project I’ve been working on, I stumbled across something that I haven’t seen before: one AJAX call was possible to make in the web page, but after that it stopped working.

To give you some context: this (naturally) only occurred in Internet Explorer, both with the native XMLHttpRequest in IE 7 and an ActiveXObject for IE 6. A sidenote, but if anyone’s wondering: I use a try...catch statement to find the supported ActiveXObject in the visiting web browser:


try {
    this.xmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");
}
catch(e){
    try {
        this.xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
    }
    catch(e){
        try {
            this.xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch(e){
            this.xmlHttp = null;
        }
    }
}

My unsuccessful approach

My initial approach was to create the object once, and then use it for the consecutive calls when needed. This would be opposed to creating an object, making the calls and then deleting the object for each AJAX call. Silly little me thought this would be good for performance, and basically just refrain from superfluous object creation. But, apparently, the call just “stuck” in IE after it was completed, preventing the possibility to make any more calls.

A cut down version of the (originally object-oriented and fancy ;-)) code was:


var xmlHttp = null;
/*
    This function was called as soon as the page
    had loaded, so xmlHttp was always available
*/    
function createXMLHttpRequest(){
    if(typeof XMLHttpRequest != "undefined"){
        xmlHttp = new XMLHttpRequest();
    }
    else if(typeof window.ActiveXObject != "undefined"){
        try {
            xmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");
        }
        catch(e){
            try {
                xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
            }
            catch(e){
                try {
                    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                }
                catch(e){
                    xmlHttp = null;
                }
            }
        }
    }
}

function getTheContent(url){
	xmlHttp.abort(); // Tried both with and without this
	xmlHttp.onreadystatechange = contentIsReady;
	xmlHttp.open("GET", url, true);
	xmlHttp.send(null);
}

function contentIsReady(){
    if(xmlHttp && xmlHttp.readyState == 4){
        // Do some magic
    }
}

The one that worked

This is basically the same code as above, with the exception that the XMLHttpRequest object is created each time, and discarded once the call is done.


function createXMLHttpRequest(){
    var xmlHttp = null;
    if(typeof XMLHttpRequest != "undefined"){
        xmlHttp = new XMLHttpRequest();
    }
    else if(typeof window.ActiveXObject != "undefined"){
        try {
            xmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");
        }
        catch(e){
            try {
                xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
            }
            catch(e){
                try {
                    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                }
                catch(e){
                    xmlHttp = null;
                }
            }
        }
    }
    return xmlHttp;
}

function getTheContent(url){
    if(xmlHttp){
        xmlHttp.abort();
    }
    // Create the object each time a call is about to be made
    if(createXMLHttpRequest()){
	xmlHttp.onreadystatechange = contentIsReady;
	xmlHttp.open("GET", url, true);
	xmlHttp.send(null);
    }
}

function contentIsReady(){
    if(xmlHttp && xmlHttp.readyState == 4){
        // Do some magic
        
        // Delete the object after the call is done
        xmlHttp = null;
    }
}

Any ides why it doesn’t work?

With those examples I have to ask you: do you have any ideas why it wouldn’t work with a single object creation? How come one call is ok, but it then just stops working?

32 Comments

  • I did a Google Maps mashup about 1,5 years ago, you know, when they we're still hot. For that I used a few global variabels, one of which was a xmlHttp object. Since I didn't know much about JS at the time I made the requests synchronous to avoid scope issues. It worked like a charm.

  • Ash Searle says:

    Total guess… but I notice you're setting <code>onreadystatechange</code> before calling <code>open</code> (which is different to the way things are normally done: e.g. prototype library, my own code, etc.)

    I don't suppose setting the handler after calling <code>open</code> fixes things does it?

  • Dave O'Brien says:

    Ash Searle's right, IE resets the object when you call open() so you need to have your onreadystatechange after it. On a side note, you really should check your code before posting (I'm referring to getTheContent() ).

  • Robert Nyman says:

    Sjors,

    I know that when making synchronous requests, there is (or at least was) a problem with Firefox and the <code>onreadystate</code> event.

    Ash,

    Interesting. I know that the <code>onreadystatechange</code> event needs to be applied before the <code>send</code> event, but then not necessarily before the <code>open</code> method.

    Thanks! I'll try it out!

    Dave,

    Thanks for confirming that.

    …you really should check your code before posting—

    Aww. I was trying to blog on a bouncy train. I have no idea where the extra indentation and erroneous <code>}</code> came from… πŸ˜‰

    Anyway, I've updated the post with correct code.

  • AndrÃ&A says:

    I know I've bumped into this problem with a project of mine but didn't find any solution. Could that be it? So it means that you only open() once? Have to try it myself. Thanks for the tip, Ash.

  • myf says:

    (totally nitpickerish off-topic; feel free to delete it)<blockquote cite="http://www.robertnyman.com/2007/03/26/indentation-of-code/">I would never use any formatting like that (…) “Huh?!â€Â Is it really four-spaces indent I see in your examples? πŸ™‚

  • Robert Nyman says:

    myf,

    Ha ha! πŸ˜€

    Actually, it's quite interesting. The pre-formatted source code is indeed indented with tabs, but when saving/publishing a post with WordPress, it replaces the tabs with spaces.

    Man, that is a hawk eye thing to notice something like that!

  • Remy Sharp says:

    Hi – have you tried cache busting? I was building a "live price" system and found that IE would show the price changes once and then the requests wouldn't continue.

    I changed the URL and appended <code>Math.random()</code> and it fixed the problem outright.

  • Robert Nyman says:

    Remy,

    Interesting approach. If the problem persists, it's definitely something to try.

  • Ash Searle says:

    Remy / Robert,

    If you find IE's using cached XHR responses, you've got a couple of things to looks at before resorting to brute-force timestamps:

    1) Should you be using <code>"POST"</code> instead of <code>"GET"</code>? (does the XHR request cause a permanent change on the server? If so, you should be using <code>"POST"</code> anyway.)

    2) Is the server setting appropriate <code>Expires</code> and <code>Cache-Control</code> headers on the XHR response?

  • Robert Nyman says:

    Ash,

    Very good points! Thank you for contributing!

  • It's definitely the fact that you assign the <code>onreadystatechange</code> handler before the <code>open</code> method invocation. This was mentioned a while ago on the IE Team's blog:

    Xmlhttp.open has a “resetâ€Â semantic so the second open() call on the same object will abort the previous connection, disconnect previous event handler, and reset the object.

    (This means that you don't need to invoke the <code>abort</code> method, either.)

  • Robert Nyman says:

    Nick,

    Thanks for the info! However, the <code>abort</code> method should still be important for other web browsers, right? To stop the current connection and start a new one.

  • Robert,

    The W3C Draft Spec for the <code>open()</code> method ofXMLHttpRequest states that:

    <blockquote cite="http://www.w3.org/TR/XMLHttpRequest/#xmlhttprequest-members"&gt;

    Invoking this method must initialize the object by remembering the method, url, async (defaulting to true if omitted), user (defaulting to null if omitted), and password (defaulting to null if omitted) arguments, setting the state to open, resetting the responseText, responseXML, status, and statusText attributes to their initial values, and resetting the list of request headers.

    In addition, when the state is not uninitialized, all members of the object with the exception of onreadystate must be set to their initial values and user agents must behave as if abort() was invoked.

    My rough and ready tests indicate that this isn't necessarily how things work in the wild though… Firefox in particular seems to get a little upset if <code>abort()</code> isn't invoked, but gets upset a different way if it is πŸ™

  • Robert Nyman says:

    Nick,

    Hmm, interesting. When it comes to Firefox and the <code>abort</code> method (a problem I've been facing as today, actually), the approach and description by Peter-Paul Koch described in XMLHTTP notes: abort() and Mozilla bug solves it. πŸ™‚

  • Thanks! This blog entry saved me a good hour of debugging! πŸ™‚

  • Robert Nyman says:

    John,

    I'm glad it helped!

  • royc says:

    i think the we dont need such complication if the a slight change is made:

    getTheContent()

    {

    createXMLHttpRequest();

    blah..

    blah..

    blah..

    }

  • Robert Nyman says:

    LongStone,

    At the end, it was the order of the code that was the culprit.

  • LongStone says:

    Seems like the IE cache issue @ first glance.

    It will cache EVERYTHING in the GET scope, setting timeouts/headers etc doesn't matter.

    I just set the "ID" of may AJAX request (I'm using conio which still uses the initial JSON-RPC spec) to a random number (could append to ID if you're actually using it for something)

  • Gurdi says:

    Thanks Ash and Dale! Thanks Robert!

    I had the same problem, IE7 allowed only one request and then XHR "died". Solution really was to set XHR.onreadystatechange after calling XHR.open();

    Cheers!

  • Robert Nyman says:

    Gurdi,

    I'm glad it helped!

  • Nik says:

    I have tried everything that you guys have mentioned above … but am still getting "permission Denied" error beyond the first XHR Call

    πŸ™

  • Robert Nyman says:

    Nik,

    The reason might that you try to run files iles locally, which won't work in Internet Explorer. Makes sure they're on a server and things should work fine for you (I hope).

  • Charles Chen says:

    I'm experiencing this issue as well with IE7. I tried both prototype and jQuery in an attempt to see if it was isolated to prototype.

    Still haven't figured this one out yet and it's quite puzzling (especially since Microsoft's AJAX libraries with ASP.NET AJAX work fine…).

    The odd thing is that it all works fine if I'm running Fiddler (which kind of makes me wonder whether it's a caching issue). As soon as I shut off Fiddler, I get 400/"Bad Request" errors.

    In order to inspect the HTTP request, I had to use WireShark on the server (since Fiddler on the client was causing it to work for every request). It turns out that the requests are drastically different on successful attempts and failed attempts. Notably, even though the requests are identical, the content length is always 0 on failed requests.

    Here is a failed request:

    <code>

    POST /FirstPoint.Common.Services.UserDriven/UserDrivenService/GetRoutes HTTP/1.1

    Accept: text/javascript, text/html, application/xml, text/xml, */*

    Accept-Language: en-us

    x-prototype-version: 1.6.0.2

    Referer: http://zorch6:2345/_layouts/FirstPointUserDrivenRoutes/Default.aspx

    x-requested-with: XMLHttpRequest

    Content-Type: application/json; charset=UTF-8

    UA-CPU: x86

    Accept-Encoding: gzip, deflate

    User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; WOW64; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; InfoPath.2; MS-RTC LM 8; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)

    Host: zorch6:2345

    Content-Length: 0

    Connection: Keep-Alive

    Cache-Control: no-cache

    Cookie: MSOWebPartPage_AnonymousAccessCookie=2345; WSS_KeepSessionAuthenticated=2345; previousLoggedInAs=; loginAsDifferentAttemptCount=

    Authorization: NTLM TlRMTVNTUAABAAAAB7IIogkACQA0AAAADAAMACgAAAAFAs4OAAAAD0NIQVJMRVMtUVVBRFdPUktHUk9VUA==

    </code>

    Here is a successful request:

    <code>

    POST /FirstPoint.Common.Services.UserDriven/UserDrivenService/GetRoutes HTTP/1.1

    Accept: text/javascript, text/html, application/xml, text/xml, */*

    Accept-Language: en-us

    x-prototype-version: 1.6.0.2

    Referer: http://zorch6:2345/_layouts/FirstPointUserDrivenRoutes/Default.aspx

    x-requested-with: XMLHttpRequest

    Content-Type: application/json; charset=UTF-8

    UA-CPU: x86

    Accept-Encoding: gzip, deflate

    User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; WOW64; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; InfoPath.2; MS-RTC LM 8; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)

    Host: zorch6:2345

    Content-Length: 16

    Proxy-Connection: Keep-Alive

    Pragma: no-cache

    Cookie: MSOWebPartPage_AnonymousAccessCookie=2345; WSS_KeepSessionAuthenticated=2345; previousLoggedInAs=; loginAsDifferentAttemptCount=

    </code>

    And here is a request from FF (in which the AJAX request works flawlessly):

    <code>

    POST /FirstPoint.Common.Services.UserDriven/UserDrivenService/GetRoutes HTTP/1.1

    Accept: text/javascript, text/html, application/xml, text/xml, */*

    Accept-Language: en-us

    x-prototype-version: 1.6.0.2

    Referer: http://zorch6:2345/_layouts/FirstPointUserDrivenRoutes/Default.aspx

    x-requested-with: XMLHttpRequest

    Content-Type: application/json; charset=UTF-8

    UA-CPU: x86

    Accept-Encoding: gzip, deflate

    User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; WOW64; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; InfoPath.2; MS-RTC LM 8; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)

    Host: zorch6:2345

    Content-Length: 16

    Proxy-Connection: Keep-Alive

    Pragma: no-cache

    Cookie: MSOWebPartPage_AnonymousAccessCookie=2345; WSS_KeepSessionAuthenticated=2345; previousLoggedInAs=; loginAsDifferentAttemptCount=

    </code>

    Prototype has the correct ordering of calls to XHR to initiate the request, so I'm baffled by what could be causing this issue.

    Any thoughts would be appreciated.

    – Chuck

  • Brian Giedt says:

    Charles,

    I’m having the same problem as you and think I have some idea about what’s happening but not the cause.

    Your failed request includes a “Authorization: NTLM …” line and I’ll be that the payload of your request never made it to the server. If you aren’t sending anything with the send() method, then you’re probably not noticing it.

    I believe that the browser is attempting to send a request to authenticate the request before it actually bothers to post the data your requesting.

    At least that’s what I’m seeing in my code when I look at it with Fiddler.

  • spentamanyu says:

    For those who reached this paged from google with that issue, the one with the crappy IE8 working only when you refresh the page. I solved (find the solution in antoher page) moving the meta tag

    just afer the tag. So it must be this way

    and not this other way arround (next example is the one who troubles)

    Hope its helpfull to someone.

  • spentamanyu says:

    Damn it:

    proper way; html,head,title,metatag,script

    wrong way; html. head. title.script. metatag

  • mailderemi says:

    http://ajaxian.com/archives/ajax-ie-caching-issue

    this issue was solved by using:

    <code>myReq.open("GET", url, true);

    myReq.setRequestHeader("If-Modified-Since", "Thu, 1 Jan 1970 00:00:00 GMT");

    myReq.setRequestHeader("Cache-Control", "no-cache");

    </code>

  • Dez says:

    @spentamanyu

    I LOVE YOU MAN!!!!

    You solved that problem that was getting me mad! πŸ™‚

  • lukaskasha says:

    I wrote a wrapper for Ajax requests a long time ago, and all of a sudden it stopped working – but only on one site, and it would work if I hit refresh (first requests were being fired with the onload event).

    I’m so sick of hearing the answer ‘Just use jQuery’. So I took a look at jQuery and it turns out I’m doing the same thing jQuery is, so it should work just fine. Some of us like to understand how JavaScript works, not just rely on other people who understand it.

    After hours – HOURS – of troubleshooting and searching, I returned to this page for the fourth time and scrolled down to the bottom, ready to post a comment and see if there was anyone who could help me. At first, @spentamanyu’s comments confused me. So I re-read them a few times before I got the gist of what he was saying, and I figured I’d give it a shot. I removed all the metatags the designer had put in the HTML. AND IT WORKED LIKE A CHARM!!!

    I can’t believe that my problem was the d*mn metatags.

    I haven’t yet gone through it to find out which metatag was the problem (I had them in the html in the order he specified should work), but as soon as I find out, I’ll post it back here.

Leave a Reply to myf Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.