views:

783

answers:

4

(This question is similar to this one, but it's for using XMLHttpRequest instead of an iframe for Comet.)

I'm starting an async long poll like this:

var xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.send();

If I do this inside <script>...</script> in the head, it will cause the document to keep loading forever. (I'm testing this in Safari on Mac OS X and the iPhone, and it's the only browser I need to support).

Using DOMContentLoaded or load events won't work.

Using a setTimeout with a large enough delay will work. 0 won't, 1000 will, 100 will some times and not other times. I don't feel comfortable with this.

The only way I found that works is the combination of both:

document.addEventListener('DOMContentLoaded', function () {
    setTimeout(function () {
        var xhr = new XMLHttpRequest();
        xhr.open('POST', url);
        xhr.send();
    }, 0);
});

I guess this solves the problem for now, but I'm still afraid it will break in the future. // Edit: this doesn't work reliably either.

Does anyone know of a more reliable way?

+1  A: 

When you say:

...it will cause the document to keep loading forever.

What exactly do you mean by this? Do you mean that the progress bar never finishes? Or is the actual document not even fully visible? It's not likely that an AJAX request would stop the entire document from loading, but anyway ...

Case 1: The progress bar never finishes

This is likely caused by the AJAX request never completing. Have you tried looking at the AJAX request in a Firebug console? It will show you both the request made and the server's response. I would start here just to make sure the server is sending something back.

Case 2: The document elements are not fully loaded/visible

You can try putting your <script> tag right before the </body> tag.

When you do this, the DOM tree is fully loaded before the script even executes, you don't have to wait for 'domready' or 'onload' to fire. Also, keep in mind that the DOM tree being loaded does not mean DOM content (such as images) have fully loaded (you would need to wait for 'onload' to be sure of that)

In either case, I would try putting the script right before the </body> tag, so the DOM skeleton has a chance to be built.

Side note:

Are you viewing your page with any type of debugger? (Firebug, Webkit Inspector). Webkit Inspector in particular will actually stop the entire document from processing if you have the console open AND a JavaScript error is encountered. Do you see any JavaScript errors in the console? Does the page load if you turn off your debuggers?

Matt
I mean 1, the progress bar/spinner never finishes. And of course, the XHR never finishes; I'm doing Comet, that's the point.
Jaka Jančar
A: 

Could you try to use the load event instead of the DOMContentLoaded event. It should make a difference as it's only called when the page is completely loaded.

var xhr;
document.addEventListener('load', function () {
    xhr = new XMLHttpRequest();
    xhr.open('POST', url);
    xhr.send();
});

This should work. However, I don't work with Comet very often, so I don't have any way to test it at the moment.

EDIT: Sorry, just saw that Matt had already said this.

dutchflyboy
I've tried load too, as I've said in the question.
Jaka Jančar
+1  A: 

I'm not sure, but it seems that if the browser shows that it's still downloading then that's entirely correct - isn't that basically what Comet programming is? The server is still sending unbuffered content and when that streams in a block of javascript it's executed, allowing the server to push events to the client browser.

In the Ajax early days (for instance on IE6 where XMLHttpRequest was a separate ActiveX object) I'd of expected the browser to not know that it was still waiting.

But in Safari 4, Chrome, FX3.5 and all the modern browsers the XMLHttpRequest is built in - it knows that it's still waiting for the server to still stream its content, exactly as it would with and <IFrame>

In short - I'd expect any Comet approach to show that the browser was still downloading because it is. I'd expect any workaround you find to get fixed in future builds because Comet is essentially a hack to get a server-push model working.

However they have started to built real server-push support into HTML 5.

Does mobile Webkit support the HTML 5 draft event-source tag yet? If so you could potentially try that.

Then you would have something like this:

<!-- new HTML 5 tag supporting server-push -->
<event-source src="http://myPushService.com" id="service">

<script type="text/javascript">

    function handleServiceEvent(event) {
        // do stuff
    }

    // tell browser to fire handleServiceEvent in response to server-push
    document.getElementById('service').addEventListener('event name', handleServiceEvent, false);
</script>
Keith
"I'd expect any Comet approach to show that the browser was still downloading". By that logic (if anything is loading => page isn't ready), if an async request was started as a response to a user click, the spinner/progress bar would reappear, but it doesn't.Unfortunately, event-source doesn't seem to be supported on even the latest WebKit nightly. Even though your answer doesn't help me right now, it will hopefully be the right one in a year or so, so I'm accepting it :)
Jaka Jančar
Cheers - on the progress I think the significant difference is that a user click has initiated a new process, rather than the page load (which is why sometimes the wait works). Actually I'd like to see better support for async events - perhaps the browser should show something more consistently when the user initiates something with an `XMLHttpRequest`. That way there wouldn't be 1000nds of different spinners on different sites and fewer sites that appeared to do nothing on a click and then load extra content after a few seconds.
Keith
A: 

It looks like in some browsers, the open(...) method sends ajax request synchronously by default, if no third parameter is specified? Try:

xhr.open('POST', url, true);

to explicitly make the call asynchronous.

RMorrisey
That's not it. Safari defaults to async properly.
Jaka Jančar