views:

442

answers:

3

I'm trying to implement a iGoogle like dashboard interface using widgets that get their content from other sites using JSONP calls.

The problem is that if the first widget that calls the "$.ajax" takes 8 seconds to get the content back, it seems that the callbacks of the other widgets will only be called after the callback of the first widget gets executed. For the user experience, it would be better if the widgets could be displayed as soon as they get the content back from the remote sites, and not wait for those that were scheduled before to complete.

Is there a way I can do that?

EDIT : I use jquery 1.4.1.

I tested on Chrome and the behaviour seems to be different than on Firefox.

Here is a script that I've made up to try to get what happens :

  function showTime(add) { console.log(getTime() + ': ' + add); }
  function getNow() { return new Date().getTime(); }
  initialTime = getNow();
  function getTime() { return getNow() - initialTime; }
  function display(data) {  showTime('received a response'); }

  showTime("Launched a request");
  jQuery.getJSON("http://localhost:51223/WaitXSeconds/3?callback=?", display);
  showTime("Launched a request");
  jQuery.getJSON("http://localhost:51223/WaitXSeconds/4?callback=?", display);
  showTime("Launched a request");
  jQuery.getJSON("http://localhost:63372/WaitXSeconds/9?callback=?", display);
  showTime("Launched a request");
  jQuery.getJSON("http://services.digg.com/stories/top?appkey=http%3A%2F%2Fmashup.com&type=javascript&callback=?", display);
  showTime("Launched a request");
  jQuery.getJSON("http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US&callback=?", display);

The first three calls are just fake calls that wait the specified number of seconds. Note that I use two different servers implementing this method.

Here is the result in the console on Firefox 3.6.2 :

0: Launched a request
3: Launched a request
6: Launched a request
11: Launched a request
14: Launched a request
3027: received a response
7096: received a response
9034: received a response
9037: received a response
9039: received a response

.. and here is the result in Chrome 4.1.249.1036 (41514) :

1: Launched a request
2: Launched a request
3: Launched a request
4: Launched a request
5: Launched a request
165: received a response
642: received a response
3145: received a response
7587: received a response
9157: received a response

It seems that in Firefox, the two requests to the two public APIs get called at the end, after all the other calls succeed.

Chrome, on the other hand, manages to execute the callback as soon as it receives the response.

On both browsers, when the request happen on the same server, they are not done in parallel. They are scheduled one after the other. But I guess this is a reasonable behaviour.

Can anybody explain Firefox's behaviour or has any hack to go around this?

+1  A: 

According to the jQuery.ajax() page:

The first letter in Ajax stands for "asynchronous," meaning that the operation occurs in parallel and the order of completion is not guaranteed.

I don't know why the latter-called widgets are returning later, but I don't think it's to do with the jQuery call, unless, as Peter suggested, you've explicitly set async to false.

Skilldrick
+1  A: 

By default $.ajax is asynchronous.

asyncBoolean Default: true

Make sure you don't have it set to false. Debug the XHR requests using Firebug to see if the requests are correctly sent and why the dom is not getting updated.

You could have a look at this Tutorial to see how to use these tools and how to discover what's wrong with your GUI.

al nik
+2  A: 

In Firefox, if one of concurrent JSONP request isn't finished, then all successive JSONP request aren't executed, even if their responses have already arrived and written into these tags. This is because <script> tags, used by JSONP, executed synchronously in Firefox. So if one <script> isn't finished, successive <script> tags aren't executed, even if they are populated with response data.

The solution is to wrap concurrent JSONP requests by iFrame. There is a project called jquery-jsonp that solves this issue.

Here is a simplified version of iFramed JSONP:

var jsc = (new Date()).getTime();
function sendJsonpRequest(url, data, callback) {
    var iframe = document.createElement("iframe");
    var $iframe = jQuery(iframe);
    $iframe.css("display", "none");
    jQuery("head").append($iframe);

    var iframeWindow = iframe.contentWindow;
    var iframeDocument = iframeWindow.document;

    iframeDocument.open();
    iframeDocument.write("<html><head></head><body></body></html>");
    iframeDocument.close();

    var jsonp = "jsonp" + jsc++;
    var url = url + "?callback=" + jsonp;
    var params = jQuery.param(data);
    if (params) {
        url += "&" + params;
    }

    // Handle JSONP-style loading
    iframeWindow[jsonp] = function(data){
        if (callback) {
            callback(data);
        }
        // Garbage collect
        iframeWindow[jsonp] = undefined;
        try{ delete iframeWindow[jsonp]; } catch(e){}
        if (head) {
            head.removeChild(script);
        }
        $iframe.remove();
    };

    var head = iframeDocument.getElementsByTagName("head")[0];
    var script = iframeDocument.createElement("script");
    script.src = url;

    head.appendChild(script);
}
smnh
Great answer!I had already switched to jquery-jsonp and it now works like a charm.It's still good to know what was happening.Thanks.
lau
I want to add here some note. I saw that some guys wrote that jQuery.ajax() should be asynchronous. Well, they are right as long as we are talking about AJAX. But jQuery.ajax({dataType:"script"}) or jQuery.ajax({dataType:"jsonp"}) or jQuery.getScript() or jQuery.getJSON with callback=? parameter, these methods are not AJAX methods, but <script> tag injections. Which is again, not synchronous in Firefox.
smnh