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.
// 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);
}
<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>
I've got the sample code at JSFiddle if you want to bang on it there.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With