4

Here is my AJAX function:

/**
 * Send an AJAX request
 *
 * @param url      The URL to call (located in the /ajax/ directory)
 * @param data     The data to send (will be serialised with JSON)
 * @param callback The function to call with the response text
 * @param silent   If true, doesn't show errors to the user
 * @param loader   The element containing "Loading... Please wait"
 */
AJAX = function(url,data,callback,silent,loader) {
    var a,
        attempt = 0,
        rsc = function() {
            if( a.readyState == 4) {
                if( a.status != 200) {
                    if( a.status > 999) { // IE sometimes throws 12152
                        attempt++;
                        if( attempt < 5)
                            send();
                        else if( !silent) {
                            alert("HTTP Error "+a.status+" "+a.statusText+"<br />Failed to access "+url);
                        }
                    }
                    else if(!silent) {
                        alert("HTTP Error "+a.status+" "+a.statusText+"\nFailed to access "+url);
                    }
                }
                else {
                    callback(JSON.parse(a.responseText));
                }
            }
        },
        to = function() {
            a.abort();
            attempt++;
            if( attempt < 5)
                send();
            else if( !silent) {
                alert("Request Timeout\nFailed to access "+url);
            }
        };
    data = JSON.stringify(data);
    var send = function() {
        if( loader && attempt != 0) {
            loader.children[0].firstChild.nodeValue = "Error... retrying...";
            loader.children[1].firstChild.nodeValue = "Attempt "+(attempt+1)+" of 5";
        }
        a = new XMLHttpRequest();
        a.open("POST","/ajax/"+url,true);
        a.onreadystatechange = rsc;
        a.timeout = 5000;
        a.ontimeout = to;
        a.setRequestHeader("Content-Type","application/json");
        a.send(data);
    };
    send();
};

The general idea is to attempt the request up to five times. Sometimes IE fails with an unusual HTTP error (12xxx), and sometimes the server may fail to respond.

The problem I'm having is that the abort() call doesn't appear to be aborting the connection. To test, I made a simple PHP script:

<?php
    sleep(60);
    touch("test/".uniqid());
    die("Request completed.");
?>

The touch() call creates a file with the current uniqid() - by looking at the modification time I can see the time the sleep(60) ended.

Expected behaviour:

The request is sent
After five seconds, the text changes to "Error... Retying... Attempt 2/5"
Repeat the above up until Attempt 5/5, then fail.
The five calls to the PHP file are aborted, and either there will be five files in the "test" folder, spaced 5 seconds apart, or there will be none because ignore_user_abort is off.

Observed behaviour (in IE9):

The request is sent
The attempt text appears and changes as it should
After five attempts, the error message is displayed
I am unable to load any pages for five whole minutes.
On the server, there are five files spaced one minute apart

I don't know what to make of this, because on the server side Requests 3, 4 and 5 are being sent minutes after the "Timeout" error message is shown on the browser.

If it makes any difference, the page making the AJAX calls is in an iframe. Reloading the iframe (using iframe.contentWindow.location.reload() does NOT fix the issue, it still waits for those five requests to go through.

Why is this happening? How can I fix it?

EDIT: I've run the test again using Developer Tools to monitor network activity. The result is:

URL          Method   Result     Type   Received  Taken   Initiator
/ajax/testto          (Aborted)              0 B  < 1 ms (Pending...)
/ajax/testto          (Aborted)              0 B  125 ms (Pending...)
/ajax/testto          (Aborted)              0 B  125 ms (Pending...)
/ajax/testto          (Aborted)              0 B  125 ms (Pending...)
/ajax/testto          (Aborted)              0 B  124 ms (Pending...)
Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592

4 Answers4

4

The problem seems to be that timeout and ontimeout aren't yet part of some implementations:

var hasTimeout = 'timeout' in new XMLHttpRequest(); // false

At least, it's not in Chrome 16 or Firefox 7. And, this should return true given the requirements:

The timeout attribute must return its value. Initially its value must be zero.

Both have been part of the XHR 2 spec since Sept 7, 2010. But, as the "Level 2" spec has been been around since Feb 25, 2008 and is still a "Working Draft," there's not really a guarantee that any implementation would be up-to-date with the spec.


Without those available to you, you can try instead using onabort and setTimeout (as you stated in your comment):

// snip

to = function() {
    attempt++;
    if( attempt < 5)
        send();
    else if( !silent) {
        console.log("Request Timeout\nFailed to access "+url);
    }
};

// snip

var send = function() {
    if( loader && attempt != 0) {
        loader.children[0].firstChild.nodeValue = "Error... retrying...";
        loader.children[1].firstChild.nodeValue = "Attempt "+(attempt+1)+" of 5";
    }
    a = new XMLHttpRequest();
    a.open("POST","/ajax/"+url,true);
    a.onreadystatechange = rsc;
    setTimeout(function () {     /* vs. a.timeout */
        if (a.readyState < 4) {
            a.abort();
        }
    }, 5000);
    a.onabort = to;              /* vs. a.ontimeout */
    a.setRequestHeader("Content-Type","application/json");
    a.send(data);
    console.log('HTTP Requesting: %s', url);
};

// snip

Example: http://jsfiddle.net/AmQGM/2/ -- The ?delay=2 should finish, while the ?delay=10 expires its 5 tries.

Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
  • `"timeout" in new XMLHttpRequest()` returns `true` in the console. According to MSDN it's been there since IE7. I'm not so worried about the `timeout` support because I can always use a `setTimeout` to emulate that. I'm more worried about the `abort()` not working as it should. – Niet the Dark Absol Jan 25 '12 at 00:10
0

I had the same problem, it turned out to be the issue with PHP session. If the session is open, all other requests from the same session must wait.

<?php
    session_write_close();
    sleep(60);
    touch("test/".uniqid());
    die("Request completed.");
?>

This, of course, would not help you if you haven't started the session :)

Sabo
  • 1,635
  • 1
  • 19
  • 24
0

Before aborting the request, try setting

a.onreadystatechange = function() {};

after that, also add a check around calling abort:

if( a.readyState > 0 && a.readyState < 4 ) {
    a.abort();
}
yas
  • 3,520
  • 4
  • 25
  • 38
0

When running your code, I got the error c00c023f. When I googled it, it came up with this answer:

http://www.enkeladress.com/article.php/internetexplorer9jscripterror

This sounds very similar to what you're experiencing.

Here's a SO question which has the same problems, with a solution (also based on the above link, but with additional information): IE 9 Javascript error c00c023f

Community
  • 1
  • 1
Tango Bravo
  • 3,221
  • 3
  • 22
  • 44
  • Hmm, while that does seem promising, I gave it a try and it still didn't help. I really think the issue is that the connection isn't being aborted, or something along those lines... – Niet the Dark Absol Feb 02 '12 at 06:17
  • Yeah. I think it's an issue with the `a` variable. The `rsc()` function is calling it, and that's where I'm getting an error. Did you set up the check for `abort` before you test the readyState? – Tango Bravo Feb 02 '12 at 06:21
  • Thing is, I'm not getting that error. I don't get any errors, just the inability to load pages... – Niet the Dark Absol Feb 02 '12 at 17:08
  • That's weird, because I am. Hm, you say you're using IE9? I am too. I don't know what to say. – Tango Bravo Feb 02 '12 at 17:12
  • Hmm, interesting. I just got a legit failed request (500 Internal Server Error) and the timeout/abort/retry code worked perfectly. Maybe all of these problems just stemmed from the fact I was using a PHP script to sleep before returning the error. I'm going to award the bounty to this question on account of it appears to have the most research behind it, although the question will remain open in case anyone can answer why the original problem occurred. – Niet the Dark Absol Feb 02 '12 at 17:54