2
votes

I set up the usual XMLHttpRequest and everything works fine so far, I can retrieve data and readyState changes and does all sorts of things.

One thing that doesn't happen though is that readyState ever triggers the onreadystatechange() function when it reaches 0.

Now I'm wondering... why?

I looked into the XMLHttpRequest MDN entry but there's no mention of why onreadystatechange should NOT fire when readyState reaches 0. I also found no other information on the topic...

Snippet:

// Inside a class, this = class instance
this.socket = new XMLHttpRequest();

// For easy access of this class instance in onreadystatechange
var client = this;

// Write current readyState to console
this.socket.onreadystatechange = function ()
{
    // Adding this just to verify there are no errors in if block
    console.log("readyState: "+client.socket.readyState);

    if(client.socket.readyState === 0)
    {
        console.log("readyState is 0");
    }
    else
    {
        console.log("readyState is "+client.socket.readyState);
    }
}

When the XMLHttpRequest socket is done processing the request, the XMLHttpRequest instance will have the value readyState===0, at least Firefox' JS console tells me that.

After doing a request, the JS console lists the log messages for readyStates 1-4 correctly, but no message for readyState 0, not before the request and not afterwards.

In theory, when the readyState changes from 4 back to 0, shouldn't the onreadystatechange() function be triggered? Checked this on macOS with current FF and Safari, as well as on Win with IE11, the same behaviour everywhere, onreadystatechange() never fires for readyState 0.

Is XMLHttpRequest intentionally not doing that, by design, or am I doing something wrong? Grateful for any helpful input. :)

(Please no "use jQuery" comments or similar, thx.)

UPDATE:

Turns out the readyState stays at 4 forever after a successful or failed request is completed, verified in FF, IE, Safari.

However, the readyState will return from > 0 to 0 if an ongoing request is aborted. So a readystatechange does happen, but the onreadystatechange is not fired, verified this in FF and Safari.

UPDATE 3:

According to the XMLHttpRequest spec, when XHR is in readyState 3 (receiving) and aborted, the XHR should set readyState to 4 (done) with an onreadystatechange event, issue a network error and then set readyState to 0 (unsent) with no onreadystatechange event.

But when I abort an XHR that is in readyState 3 (receiving), it will first set readyState to 4 (done) with an onreadystatechange event and HTTP status 200 (OK), then it will trigger onabort and onloadend and reset readyState to 0 (unsent) ... but at no point is an onerror Event triggered.

Very confusing.

3
@Lukasas: === is valid JavaScript. It's the non-coercing equals operator. - T.J. Crowder
Ye, my bad, i was strictly thinking about php when I saw that operator. readyState shouldn't be 0 becouse that meants it's unset. So when the socket is ready, it means it is already opened so it should be set to non zero state - Lukasas
But when I .open() the socket and .send() a request, once .readyState reaches 4 and .status === 200 (means all went well) the socket goes back to .readyState 0. Firefox' JS console quite clearly displays that as its .readyState value after completing the request and going back to "idling". The readyState changes from 4 to 0, yet no onreadystatechange is triggered. - Rob
@Rob: "...the socket goes back to .readyState 0." Not for me, not on Firefox, not on Chrome. As I say in my answer, I suspect observational error. If you don't find it, you might post a minimal reproducible example showing how you're looking at it, but it makes no sense for Firefox to change the readyState from 4 back to 0, it doesn't for me, and I suspect it doesn't, period. :-) - T.J. Crowder

3 Answers

1
votes

In theory, when the readyState changes from 4 back to 0, shouldn't the onreadystatechange() function be triggered?

XHR instances are not reusable. If Firefox's XHR object takes the state from 4 back to 0 (it doesn't for me, see below), that's a Firefox-specific thing, but it's not useful, and it's just as well that it doesn't fire a useless event on it.

It makes no sense for the state to go from DONE (4) to UNSENT (0). The request isn't unsent. It's done.

Re your note in a comment:

But after aborting, it returns back to 0 --- after it was already in readyState 3 and retrieving the file...

Ah, well, that would be different — going from 3 to 0, not from 4 to 0. I see that as well if I do the same, in both Chrome and Firefox.

The spec for XHR's abort says:

  1. When aborted when you say (readyState 3), the readyState should not change back to 0; instead, it should change to 4, set the response to a network error, and fire a readystatechange event.

  2. If aborted when readyState is 4, it should change back to 0 — but the spec does not say it should fire a readystatechange event (whereas it does say that other times the state is changed by the various algorithms).

So it seems that neither Chrome nor Firefox is closely following the spec in this edge case.

0
votes

Default Nature

Readystate 0 belongs to state unsent. That is the default entry state after you create xhr object. At this time eventhandler 'onreadystatechange' is not invoked because it is designed to call only the state changes from 0 to something.

I got your case. Now i am going to answer your question **why 'onreadystatechange' not trigger when states changes from 4 to 0?

     var xhr = new XMLHttpRequest();
 //here xhr.readystate is 0
 xhr.onreadystatechange = () => {
     console.log('state', xhr.readyState)
     //**session one** 
     // console will print 1 when request opened
     // 2 when sent and response header received
     // 3 when body is receiving 
     // 4 when done
     //**session two**
     // prints 1 
     //note:readystate comes to 1 directly from 4 not 0.The state 0 is 
     //only available upto very first time you called open
 }

 //session one
 xhr.open('GET', '/api', true);
 xhr.send()

 //session two
 //here xhr.readystate is 4
 xhr.open('GET', '/api', true);
 //here xhr.readystate is 1 there is no 0 in middle.
 xhr.send();
-1
votes

0 is the value that means that the request have not been sent. You can't unsend a request so there is no way that an event fire onchange from 4 to 0. It's like you sent a mail and then wait for you to send it. That make no sense. You only can wait for an other thing to happen (for example that the postman take it, that it arrives, ...) but obviously if you wait for something thta already happened you will wait for a very long time.

Value State Description

0 UNSENT Client has been created. open() not called yet.

1 OPENED open() has been called.

2 HEADERS_RECEIVED send() has been called, and headers and status are available.

3 LOADING Downloading; responseText holds partial data.

4 DONE The operation is complete.

UPDATE

While I was searching for an answer I found this implementation of XMLHttpRequest. This show that the abort function does not dispatch any event. That's probably why you can't catch the event because it is simply not fired.

cXMLHttpRequest.prototype.abort = function() {
        // Add method sniffer
        if (cXMLHttpRequest.onabort) {
            cXMLHttpRequest.onabort.apply(this, arguments);
        }

        // BUGFIX: Gecko - unnecessary DONE when aborting
        if (this.readyState > cXMLHttpRequest.UNSENT) {
            this._aborted = true;
        }

        this._object.abort();

        // BUGFIX: IE - memory leak
        fCleanTransport(this);

        this.readyState = cXMLHttpRequest.UNSENT;

        delete this._data;

        /* if (this._async) {
        *   fQueue_remove(this);
        * }
        */
    };