3

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.

Rob
  • 81
  • 1
  • 9
  • Uhm, no. https://stackoverflow.com/questions/359494/which-equals-operator-vs-should-be-used-in-javascript-comparisons – Rob Aug 11 '17 at 11:48
  • @Lukasas: `===` is valid JavaScript. It's the non-coercing equals operator. – T.J. Crowder Aug 11 '17 at 11:49
  • 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 Aug 11 '17 at 11:53
  • 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 Aug 11 '17 at 11:56
  • @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 [mcve] 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 Aug 11 '17 at 11:58
  • @T.J.Crowder I wish I could attach a screenshot here. Your "not reusable" answer is true and the, well, "solution" to this mystery, but Firefox definitely resets the readyState to 0 in some cases. Weirdly enough, it doesn't do it for successful or failed transactions, but for those that were stopped with .abort() – Rob Aug 11 '17 at 12:06

3 Answers3

2

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Not quite. My "error" was starting a request that downloads a large file and then using .abort() to stop it. On .readyState===4 with success or failure, the socket stays at readystate 4 forever. But after aborting, it returns back to 0 --- after it was already in readyState 3 and retrieving the file. Safari does the same. – Rob Aug 11 '17 at 12:10
  • @Rob: Ah, but that's different from going from **4** back to 0. :-) I've updated the answer having looked at the spec. Interestingly, it seems the spec'd behavior isn't being followed by either Firefox or Chrome. – T.J. Crowder Aug 11 '17 at 12:34
  • Thank you for the link to the spec, I was on there a couple of times for various reasons, but never thought to look there. However... the spec says "If state is done, then set state to unsent and response to a network error." ... but ... "state done" would mean readyState 4, right? Definitely not readyState 3 which is in progress. Confusingly, I originally found this book excerpt through Google, which is a bit misleading compared to what the spec says: https://books.google.de/books?id=6TAODdEIxrgC&pg=PA1015#v=onepage&q&f=false No mention of "no onreadystatechange" anywhere. – Rob Aug 11 '17 at 12:43
  • @Rob: Right. What I'm saying is if you abort the request when it's at state 3, the spec says it should change to state 4, set the response to a network error, and fire the event. But if you abort when it's already state 4, it should change back to 0 and *not* fire the event. (I fail to see why, but that's what it says.) – T.J. Crowder Aug 11 '17 at 13:05
  • Aha, now I understand. If aborted at 3 then set to 4, fire error and readystatechange, then set to 0 without readystatechange. If aborted at 4, just set to 0 and fire no readystatechange. So far, so understood. But when my XHR is in 3 / receiving and I abort, it will set readyState to 4 with HTTP status 200 (OK), and then trigger first the onabort and then the onloadend Event. No onerror is called anywhere... – Rob Aug 11 '17 at 13:12
  • @Rob: *"If aborted at 3 then set to 4, fire error and readystatechange, then set to 0 without readystatechange"* That's not how I read it. I read it as set it to 4 and leave it that way, which neither Firefox nor Chrome does. Re not getting any event on abort, can't help you there, haven't got into that bit. But as you aborted it, hopefully you know it was aborted. :-) – T.J. Crowder Aug 11 '17 at 13:17
0

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();
Vignesh
  • 496
  • 1
  • 4
  • 13
  • Thank you for your reply. T.J. Crowder already pointed me to the [XMLHttpRequest spec](https://xhr.spec.whatwg.org/#the-abort()-method) which mentions that no onreadystatechange event will be fired, so that part of the mystery is solved. However, the spec also mentions that when .abort() is called, the readyState will be reset to 0 "if state is done", which would mean to me "if readyState is already 4". However, .abort() will reset the readyState to 0 if readyState is only 3, which is in progress and *not* (to my understanding) "done" yet. Very weird. – Rob Aug 11 '17 at 12:52
-1

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);
        * }
        */
    };
  • You have to read the whole question. :-) The OP says he sees it go back to 0 from 4 after the request is complete in Firefox, but doesn't see an event for that. If it were true, that would be a change. – T.J. Crowder Aug 11 '17 at 11:55
  • Just for the sake of completeness, I also want to mention here that I updated the original post. For successful or failed transactions that had already reached readyState 4, the readyState stays at 4 forever. But for ongoing transactions that are already in readyState 3 and then cancelled while retrieving, the readyState does return to 0. Which is, in my eyes, a change, but the onreadystatechange is still not fired. – Rob Aug 11 '17 at 12:19
  • Hum... that changes thing indeed, I did not saw that. In that case indeed there should be readyState change – Alexandre Thyvador Aug 11 '17 at 12:33
  • @AlexandreThyvador thank you for investigating. :) T.J. Crowder already pointed me to the XMLHttpRequest spec in his reply, which mentions that no onreadystatechange event is fired when .abort() is called. However, the spec says readyState will only be reset to 0 if the request was already "done" (which means to me readyState 4), yet it does so during readyState 3 (receiving) and *not* when already in readyState 4... very confusing. :) – Rob Aug 11 '17 at 12:48