3

I've noticed that the size of a file requested will effect how long the response takes for ajax calls. So if I fire 3 ajax GET requests for files of varying size, they may arrive in any order. What I want to do is guarantee the ordering when I append the files to the DOM.

How can I set up a queue system so that when I fire A1->A2->A3. I can guarantee that they are appeneded as A1->A2->A3 in that order.

For example, suppose A2 arrives before A1. I would want the action to wait upon the arrival and loading of A1.

One idea is to create a status checker using a timed callback as such

// pseudo-code
function check(ready, fund) {
    // check ready some how
    if (ready) {
        func();
    } else {
        setTimeout(function () {
            check(ready, fund);
        }, 1); // check every msec
    }
}

but this seems like a resource heavy way, as I fire the same function every 1msec, until the resources is loaded.

Is this the right path to complete this problem?

  • I know you didn't tag it, but do you per chance happen to be using jQuery? – Ian Mar 26 '13 at 17:21
  • @livingston_mechanical You should look at http://yepnopejs.com/ loader. If i understand you right – ant_Ti Mar 26 '13 at 17:24
  • You definitely don't need to be polling every millisecond to see if something's ready. But if it were possible to tap into the `onreadystatechange` of the AJAX request, you could simulate jQuery's Deferred object and use asynchronous events to listen for things being loaded in a certain order – Ian Mar 26 '13 at 17:24
  • 1
    You should have a look at promises (http://promises-aplus.github.com/promises-spec/), e.g. this implementation: https://github.com/cujojs/when. – Felix Kling Mar 26 '13 at 17:30
  • ... googled it and got lucky ... https://gist.github.com/814052/690a6b41dc8445479676b347f1ed49f4fd0b1637 ... 58 lines heavily commented ... –  Mar 26 '13 at 21:28

4 Answers4

4

status checker using a 1msec-timed callback - but this seems like a resource heavy way; Is this the right path to complete this problem?

No. You should have a look at Promises. That way, you can easily formulate it like this:

var a1 = getPromiseForAjaxResult(ressource1url);
var a2 = getPromiseForAjaxResult(ressource2url);
var a3 = getPromiseForAjaxResult(ressource3url);

a1.then(function(res) {
    append(res);
    return a2;
}).then(function(res) {
    append(res);
    return a3;
}).then(append);

For example, jQuery's .ajax function implements this.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • @livingston: That one doesn't work with with returning promises, as I used it - see https://github.com/promises-aplus/promises-spec/blob/master/implementations.md for better libs. With that gist, you'd need to `a1.then(function(res){append(res);a2.then(function(res){append(res);a3.then(append);});});` – Bergi Mar 26 '13 at 21:43
  • Actually you need to implement more than that spec, since it's only defining one function :-) If you want a very tiny lib, have a look at https://github.com/timjansen/PinkySwear.js/blob/master/pinkyswear.js – Bergi Mar 27 '13 at 13:30
2

You can try something like this:

var resourceData = {};
var resourcesLoaded = 0;

function loadResource(resource, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        var state = this.readyState;
        var responseCode = request.status;

        if(state == this.DONE && responseCode == 200) {
            callback(resource, this.responseText);
        }
    };

    xhr.open("get", resource, true);
    xhr.send();
}

//Assuming that resources is an array of path names
function loadResources(resources) {
    for(var i = 0; i < resources.length; i++) {
        loadResource(resources[i], function(resource, responseText) {

            //Store the data of the resource in to the resourceData map,
            //using the resource name as the key. Then increment the
            //resource counter.
            resourceData[resource] = responseText;
            resourcesLoaded++;

            //If the number of resources that we have loaded is equal
            //to the total number of resources, it means that we have
            //all our resources.
            if(resourcesLoaded === resources.length) {
                //Manipulate the data in the order that you desire.
                //Everything you need is inside resourceData, keyed
                //by the resource url. 
                ...
                ...
            }                    
        });
    }
}

If certain components must be loaded and executed before (like certain JS files) others, you can queue up your AJAX requests like so:

function loadResource(resource, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        var state = this.readyState;
        var responseCode = request.status;

        if(state == this.DONE && responseCode == 200) {
            //Do whatever you need to do with this.responseText
            ...
            ...

            callback();
        }
    };

    xhr.open("get", resource, true);
    xhr.send();
}

function run() {
    var resources = [
        "path/to/some/resource.html",
        "path/to/some/other/resource.html",
        ...
        "http://example.org/path/to/remote/resource.html"
    ];

    //Function that sequentially loads the resources, so that the next resource 
    //will not be loaded until first one has finished loading. I accomplish
    //this by calling the function itself in the callback to the loadResource 
    //function. This function is not truly recursive since the callback 
    //invocation (even though it is the function itself) is an independent call 
    //and therefore will not be part of the original callstack.
    function load(i) {
        if (i < resources.length) {
            loadResource(resources[i], function () {
                load(++i);
            });
        }
    }
    load(0);
}

This way, the next file will not be loaded until the previous one has finished loading.

If you cannot use any third-party libraries, you can use my solution. However, your life will probably be much easier if you do what Bergi suggested and use Promises.

Community
  • 1
  • 1
Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • This doesn't allow for all 3 AJAX requests to be sent at the same time. There's no reason for each AJAX request to wait to be sent before the previous one completes. That's just really inefficient. – Ian Mar 26 '13 at 17:34
  • @Ian If you'll notice, I mentioned that in my very first sentence that this will load files in **sequential** order. If the OP is ok with making that trade-off, then he can use this solution. Otherwise, he doesn't have to. – Vivin Paliath Mar 26 '13 at 17:35
  • It doesn't matter how they are "loaded". Either way, the OP wants them to be loaded sequentially of course, but the method which the AJAX requests are made doesn't have to be sequential. Running their "success" callbacks is the sequential part – Ian Mar 26 '13 at 17:37
  • @Ian The code I wrote needed things to be done in a sequential order, because certain resources/components had to be run *before* other ones were loaded. Either way, I've added some code that lets the OP fire off AJAX requests in one go, and doesn't wait to fire off a request until one has completed. – Vivin Paliath Mar 26 '13 at 17:46
1

There's no need to call check() every millisecond, just run it in the xhr's onreadystatechange. If you provide a bit more of your code, I can explain further.

Dan Hlavenka
  • 3,697
  • 8
  • 42
  • 60
1

I would have a queue of functions to execute and each of them checks the previous result has completed before executing.

var remoteResults[] 

function requestRemoteResouse(index, fetchFunction) {
  // the argument fetchFunction is a function that fetches the remote content
  // once the content is ready it call the passed in function with the result.
  fetchFunction(
    function(result) { 
      // add the remote result to the list of results
      remoteResults[index] = result
      // write as many results as ready.
      writeResultsWhenReady(index);
    });
}

function writeResults(index) {
  var i;
  // Execute all functions at least once
  for(i = 0; i < remoteResults.length; i++) {
    if(!remoteResults[i]) {
      return;
    }
    // Call the function that is the ith result
    // This will modify the dom.
    remoteResults[i]();
    // Blank the result to ensure we don't double execute
    // Store a function so we can do a simple boolean check.
    remoteResults[i] = function(){}; 
  }
}

requestRemoteResouse(0, [Function to fetch the first resouse]);
requestRemoteResouse(1, [Function to fetch the second resouse]);
requestRemoteResouse(2, [Function to fetch the thrid resouse]);

Please note that this is currently O(n^2) for simplicity, it would get faster but more complex if you stored an object at every index of remoteResults, which had a hasRendered property. Then you would only scan back until you found a result that had not yet occurred or one that has been rendered.

David Waters
  • 11,979
  • 7
  • 41
  • 76