HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Wait until all files are loaded asynchronously, then run code

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
untilallarewaitfilesloadedasynchronouslythencoderun

Problem

I'm an experienced programmer but not too great at JavaScript so I'm looking to see if I'm doing this 'right'. I want to have several files loaded in (Ajax or really AJAJ) and, once loaded, run some final code. My basic idea is:

var jobsToDo = 2; // global

function getAsync(url, func) {
    var client = new XMLHttpRequest();
    var handler = function() {
        func();
        jobsToDo--;
        if(jobsToDo == 0)
            allLoaded();
    };
    client.open('GET', url);
    client.onreadystatechange = handler;
    client.send();
}

getAsync('file1.json', function() {
    // process file
});
getAsync('file2.json', function() {
    // process file
});

function allLoaded() {
    // ...
}


Is this correct? Is it a reasonable solution, or are there better ways? Actually onreadystatechange already feels like the wrong thing; is there a better handler that will wait until I get a 200, or should I expand my function above to exit unless the state is appropriate?

Also, since I don't do much JavaScript these days (or all the cool kids calling it ECMAScript now?): will I run into trouble with the local function being passed around?

My current code has only one file being loaded, so I'm trying to extract the basic functionality here to keep the code DRY.

Meta: I don't know if this belongs here or on SO; feel free to move if I made the wrong choice.

Solution

1) Nah, the kids still call it JavaScript, but ECMAScript works too. More power to you for knowing both names :)

2) Yes, I'd say you should wait for a less ambiguous state to occur before considering the request done. But there's not really anything better than onreadystatechange (as far as I recall) to check for the request's success. So you still have to code a "ready-state change" handler, just make it check client.status === 200 (and possibly some headers and whatnot) before deciding what to call next.

Other than that, though, your code's good. The hardcoded 2 would need to go for this to be of general use, but the basic idea of decrementing a counter is solid.

You could take a look at the futures and promises pattern. There are several JS-implementations of this you can use.

jQuery has such an implementation (via the Deferred object) built into it's Ajax-system, allowing you to do, say:

var file1 = $.getJSON("file1.json"), // $.getJSON returns a Deferred
    file2 = $.getJSON("file2.json"), 
    all   = $.when(file1, file2);    // and $.when groups several Deferreds

// example usage - you can do the same for the individual files
all.done(function () {
  // something to call when all files have been successfully loaded
});

all.fail(function () {
  // something to call in case one or more files fail
});

all.always(function () {
  // something to always call (like, say, hiding a "loading" indicator)
});


Point is that Deferred lets you attach handlers asynchronously - the requests are sent the moment you call getJSON, but you can attach handlers whenever. And you can add several handlers to the same state, if you want. If the response has already been received, the handler will simply be called immediately.

Now, I know this is all jQuery, and perhaps you don't want to use that. If so, more power to you (again). It is however a neat approach and a simple API - or (in my opinion) one to emulate if you so choose.

Seeing @Kevindra's simple answer (go upvote!), I'm reminded of a different pattern I've used before:

// a simple factory function
 function makeCounter(limit, callback) {
   return function () {
     if( --limit === 0 ) {
       callback();
     }
   }
 }

 // ....

 // make a "done" function set up to expect 2 invocations
 // before calling its callback
 var done = makeCounter(2, function () {
   // all done!
 });

 getAsync("file1.json", function () {
   // process file and call the done() function
   done();
 )};

 getAsync("file2.json", function () {
   // process file and call the done() function
   done();
 )};

Code Snippets

var file1 = $.getJSON("file1.json"), // $.getJSON returns a Deferred
    file2 = $.getJSON("file2.json"), 
    all   = $.when(file1, file2);    // and $.when groups several Deferreds

// example usage - you can do the same for the individual files
all.done(function () {
  // something to call when all files have been successfully loaded
});

all.fail(function () {
  // something to call in case one or more files fail
});

all.always(function () {
  // something to always call (like, say, hiding a "loading" indicator)
});
// a simple factory function
 function makeCounter(limit, callback) {
   return function () {
     if( --limit === 0 ) {
       callback();
     }
   }
 }

 // ....

 // make a "done" function set up to expect 2 invocations
 // before calling its callback
 var done = makeCounter(2, function () {
   // all done!
 });

 getAsync("file1.json", function () {
   // process file and call the done() function
   done();
 )};

 getAsync("file2.json", function () {
   // process file and call the done() function
   done();
 )};

Context

StackExchange Code Review Q#45541, answer score: 4

Revisions (0)

No revisions yet.