Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scoping a variable shared by multiple async callbacks

I'm using the async.js library by Caolan McMahon and the jQueryUI progress bar to provide feedback to the user while several async calls gather data and fill in elements of a complex graph.

My question is: What's the best way to scope data that needs to be shared by the asynchronous methods?

This is a simplified example of what I'm doing. I've got it working using global variables but they disturb me a little and make jsLint complain. Passing arguments or scoping at the Document Ready function breaks it.

The counterparts to updateA() et. al. in my real code are hundreds of lines and include XHR calls.

JavaScript:

// global variables.  Bad?
var steps = 3;
var ticked = 0;
var otherCounter = 0;

$(function() {
    $('#progressbar').progressbar({
        value: 0
    });

    async.parallel([
        function(onDoneCallback) {
        updateA(onDoneCallback);},
        function(onDoneCallback) {
        updateB(onDoneCallback);},
        function(onDoneCallback) {
        updateC(onDoneCallback);}
    ], function(err, results) { // final callback when they're all done
        tickProgress('All done after ' + ticked + ' ticks.', true);
    });
});

function tickProgress(message) {
    var curvalue = $('#progressbar').progressbar('option', 'value');
    var done = false;

    if (arguments.length > 1) {
        done = arguments[1];
    }

    $('#progress_text').html(message);

    if (done) {
        $('#progressbar').progressbar('option', 'value', 100);
    }
    else {
        $('#progressbar').progressbar('option', 'value', curvalue + 100 / steps);
    }

    ticked++; // global OK here?
}

function updateA(onDoneCallback) {
    setTimeout(function() {
        $('#a').html('A is foo. otherCounter ' + otherCounter);
        tickProgress('updated A at otherCounter ' + otherCounter);
        otherCounter++;
        onDoneCallback(null, 'A done');
    }, 1000);

}

function updateB(onDoneCallback) {
    setTimeout(function() {
        $('#b').html('B is bottle. otherCounter ' + otherCounter);
        tickProgress('updated B at otherCounter ' + otherCounter);
        otherCounter++;
        onDoneCallback(null, 'B is OK');
    }, 100);
}

function updateC(onDoneCallback) {
    setTimeout(function() {
        $('#c').html('C is cauliflower. otherCounter ' + otherCounter);
        tickProgress('updated C at otherCounter ' + otherCounter);
        otherCounter++;
        onDoneCallback(null, 'C done');
    }, 2000);
}

HTML:

<p id="progress_text" style="background:yellow">Loading...</p>
<div id="progressbar"></div>
<hr />
<h2>a</h2>
<p id="a">Looking up a...</p>

<h2>b</h2>
<p id="b">Looking up b...</p>

<h2>c</h2>
<p id="c">Looking up c...</p>

Fiddle:

I've got the sample code at JSFiddle if you want to bang on it there.

like image 646
Nathan Avatar asked Dec 28 '22 01:12

Nathan


1 Answers

In generall, it's always a great idea to create your own closured Function-Context'ed "region". You can do that by wrapping a self invoking anonymous function around your application. This could look like

(function(window, document, $) {
     // all your app logic goes into here
     var steps = 3;
     var ticked = 0;
     var otherCounter = 0;

     // ...
}(this, this.document, jQuery))

That way, you never clobber the global namespace. Of course you need to have a global object sometimes, but you really should try to avoid that unless absolutly necessary.

like image 162
jAndy Avatar answered Dec 30 '22 14:12

jAndy