Forum: Rails Spinoffs (closed, excessive spam) Strange readyStates in prototype Ajax

Posted by jove4015 (Guest)
on 2008-06-26 23:55
(Received via mailing list)
Hi,

I've never posted here before so please forgive me if I'm not
observing proper decorum, yadda yadda yadda :-)

I've ran into a very, very strange bug with Prototype I wanted to
share and see if anyone else has seen this.  In a few of my projects
involving Ext JS, I'm using the TreePanel control, which uses AJAX to
load child nodes asynchronously.  Because I bundle in the prototype
library with my Ext, these AJAX calls are being processed by Prototype
(v 1.6.0.2).  In every instance where I use one of these tree panels,
I've noticed the weirdest problem where every so often, totally at
random, even though the child nodes are sent back correctly by the
server, the tree panel logs an error and the children fail to load.

I've been tracking this for weeks now and finally stumbled upon the
root cause of the problem, and it turns out to be an apparent bug in
Prototype, not in Ext:

In the Ajax.Request class, the respondToReadyState method, you have:


  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new
Ajax.Response(this);
etc etc etc...

Maybe 90% of the time, readyState here and transport.readyState are
the same (as they always should be?).  However, it turns out, maybe
10% of the time the readyState passed into the event handler is
completely different from the ready state in the XHR object
(transport.readyState).  I set up a piece of code to log this in
Firebug (but this happens in every browser, mind you):

respondToReadyState: function(readyState) {
    if (readyState != this.transport.readyState)
      console.log("readyState is " + readyState + " but transport says "
+ this.transport.readyState + "!!!!!");

And, in the course of perhaps 15 minutes of various ajax requests (not
just in my tree panel, but anywhere), I see this in my console:

readyState is 170 but transport says 1!!!!!
readyState is 7 but transport says 1!!!!!
readyState is 7 but transport says 1!!!!!
readyState is 3 but transport says 1!!!!!
readyState is 4 but transport says 1!!!!!

That last one is the real problem - because readyState is 4 but the
real readyState in the XHR object is still actually 1, the success
callback is triggered (because Ajax.Response checks
transport.readyState, not the readyState passed to
respondToReadyState), but there is no responseText (yet), so the whole
call fails.

Extremely frustrating bug to tease out, let me tell you, because
Firebug *always* seems to get the right responseText, even though
Prototype doesn't.  It was easy enough now to fix my problem, all I
did was this:

  respondToReadyState: function(readyState) {
  readyState = this.transport.readyState;
        [ continue on as normal ]

And now I have *no* problems.  But I really don't like the idea of
modifying my prototype :-(.  So what gives?  Can I really be the only
person this is happening to?  And what the heck would a readyState of
170 mean anyway?  It's super-duper-done?

Thanks for listening, and despite this little glitch, thanks so much
for Prototype!  I just can't live without my Enumerables!
Posted by jove4015 (Guest)
on 2008-06-26 23:57
(Received via mailing list)
Sorry, got a bit turned around in the middle there, I meant:

That last one is the real problem - because readyState is 4 but the
real readyState in the XHR object is still actually 1, the success
callback is triggered ( because state is Complete), but there is no
responseText (because Ajax.Response checks
transport.readyState, not the readyState passed to
respondToReadyState, and Ajax.Response is responsible for setting
Ajax.Response.responseText), so the whole
call fails.
Posted by Frederick Polgardy (Guest)
on 2008-06-27 00:06
(Received via mailing list)
There are two places in the Prototype request lifecycle that
respondToReadyState is called.  The obvious place is when an actual 
state
change is triggered:

(inside Ajax.Request.Prototype)

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState != 1)
      this.respondToReadyState(this.transport.readyState);
  }

This method is set to the value of request.transport.onreadystatechange 
when
the Ajax.Request is created.

However, if the response is asynchronous (nobody is writing synchronous
Ajax, are they?!), the Ajax.Request#request() method also fires off a 
state
change of 1 immediately (10ms) after it is created:

      if (this.options.asynchronous) {
        this.transport.onreadystatechange = 
this.onStateChange.bind(this);
        setTimeout((function() 
{this.respondToReadyState(1)}).bind(this),
10);
      }

Not sure how you're seeing a value of 170 in there.  Hmm.

-Fred

On Thu, Jun 26, 2008 at 4:56 PM, jove4015 <smweiss@gmail.com> wrote:

> call fails.
> > load child nodes asynchronously.  Because I bundle in the prototype
> > In the Ajax.Request class, the respondToReadyState method, you have:
> > (transport.readyState).  I set up a piece of code to log this in
> >
> > respondToReadyState), but there is no responseText (yet), so the whole
> >
> > And now I have *no* problems.  But I really don't like the idea of
> > modifying my prototype :-(.  So what gives?  Can I really be the only
> > person this is happening to?  And what the heck would a readyState of
> > 170 mean anyway?  It's super-duper-done?
> >
> > Thanks for listening, and despite this little glitch, thanks so much
> > for Prototype!  I just can't live without my Enumerables!
> >
>


--
Science answers questions; philosophy questions answers.
Posted by jove4015 (Guest)
on 2008-06-27 00:39
(Received via mailing list)
Yeah, these calls are definitely asynchronous...  So *both* types are
happening here.

And yeah, I really don't get it either.  I've been using Prototype for
years and I just feel like I would have seen the ramifications of this
before.  It really boggles my mind... I mean, I've found my AJAX calls
to be very reliable up until I started using these TreePanels!  I'm
starting to wonder now if there's some way in which readyState is
conflicting with some global variable set by Ext.  I definitely don't
use that variable in my own code (I do everything in namespaces now
anyway).  Also I've looked at where that readyState is being passed
from and it's coming originally from transport.readyState in the first
place, so one really *really* has to wonder how the two values manage
to become different.  But, then I wonder even more, why bother passing
the readyState when it's right there in the object itself anyway?
Seems like extra fuss for no benefit to me.
Posted by Frederick Polgardy (Guest)
on 2008-06-27 00:47
(Received via mailing list)
Maybe ExtJS mucks with the XHR prototype in some way?

On Thu, Jun 26, 2008 at 5:38 PM, jove4015 <smweiss@gmail.com> wrote:

>
> Yeah, these calls are definitely asynchronous...  So *both* types are
> happening here.


--
Science answers questions; philosophy questions answers.
Posted by jove4015 (Guest)
on 2008-06-27 00:55
(Received via mailing list)
I wish it were that simple... I just did a quick grep through all of
the Ext JS code I include and there's no reference to XMLHttpRequest
outside of comments (and certainly no reference to
XMLHttpRequest.prototype)... when I check the contents of
XMLHttpRequest.prototype in Firebug they match what I see on a page
that does not include Ext.

I'd say maybe it's Firebug but the bug that caused me to find this in
the first place happens in IE 6/7 and Safari 2/3 as well... really it
might even happen in Opera, I just haven't looked.
Posted by jove4015 (Guest)
on 2008-06-27 01:01
(Received via mailing list)
aha - found someone else who seems to have found out why:

http://prototype.lighthouseapp.com/projects/8886-p...
Posted by henry.christopher@gmail.com (Guest)
on 2008-06-27 16:04
(Received via mailing list)
Jove,

I have seen very familiar behavior in an application I'm building with
Ext.  I poll the server for a JSON response, and every once in a while
I notice that the transport.responseText is empty, and my function
would throw an error.  More details in this ticket:

http://prototype.lighthouseapp.com/projects/8886/t...

I've since refactored to use transport.responseJSON, and the problem
with that part of the application seems to have abated.  However, the
entire application is Ajax, and every so often I will have a response
come up empty.   Do the Ajax internals work the same way for
transport.responseText as for transport.responseJSON?

Also, if you have a patch, I'd love to see it!

Best,
Chris
This topic is locked and can not be replied to.