7
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Why you don't do jobsToDo++; inside getAsync() ? \$\endgroup\$ – Marco Acierno Mar 27 '14 at 20:19
  • \$\begingroup\$ @MarcoAcierno: Just to guard against the possibility that I ask for one file and it finishes before I ask for the next, in which case allLoaded() would be called prematurely. I don't think it's likely but I'd rather not risk it, ceteris paribus. \$\endgroup\$ – Charles Mar 27 '14 at 20:47
4
\$\begingroup\$

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();
 )};
\$\endgroup\$
  • \$\begingroup\$ That is a near approach with jQuery -- but yes, I'd prefer to avoid it if possible. \$\endgroup\$ – Charles Mar 27 '14 at 20:48
  • \$\begingroup\$ @Charles Gotcha. Well, the wiki page lists a number of implementations for JS, but if you fancy a programming challenge, making a simple implementation is fun. I'm actually trying that right now, just to see \$\endgroup\$ – Flambino Mar 27 '14 at 21:12
  • \$\begingroup\$ @Charles And done; here's a simple implementation - as mentioned, I just wrote it to see what it'd take, so it's not tested like the ready-made libraries out there. But it might give you some ideas \$\endgroup\$ – Flambino Mar 27 '14 at 21:30
4
\$\begingroup\$

IMO, you keep the jobsToDo counter away from your getAsync method. I think job of your getAsync method should be just to load a file and call the respective handler.

function getAsync(url, func) {
    var client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = func;
    client.send();
}

And you should separately monitor the status -

var filesLoaded = 0;
getAsync('file1.json', function() {
    // process file
    checkAllLoaded();
});
getAsync('file2.json', function() {
    // process file
    checkAllLoaded();
});

function checkAllLoaded() {
    filesLoaded += 1;
    if(filesLoaded == 2) {
       // ...
    }
    // ...
}

I don't know if it's a good solution, but I think it would help you to keep the core method getAsync simple.

\$\endgroup\$
  • 3
    \$\begingroup\$ You could make checkAllLoaded() do the counter increment too - then there's only one thing to do in each handler. \$\endgroup\$ – Flambino Mar 27 '14 at 21:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.