开发者

Scoping a variable shared by multiple async callbacks

开发者 https://www.devze.com 2023-02-08 22:56 出处:网络
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.

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开发者_Python百科>

<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.


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.


You could put the global vars into a "state" object and pass it to all the callback functions. e.g.

$(function() {
    var progressState = {
        steps: 3,
        ticked: 0,
        otherCounter: 0
    };

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

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

You would then change updateA, updateB, updateC and tickProgress to use the provided state object, instead of the global vars.


david turned me on to this approach the other day in the JS chat room. See it work at jsfiddle.

It makes use of a global object, but I like the logical encapsulation of the methods within the object better than the suggestions by jAndy and kirilloid.

var Updater = (function() {
    var steps = 3;
    var ticked = 0;
    var otherCounter = 0;

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

    }

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

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

    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 / Updater.getSteps());
        }
        Updater.tick(); // global OK here?
    }

    return {
        a: a,
        b: b,
        c: c,
        tickProgress: tickProgress,
        tick: function() {
            ticked++;
        },
        getTicks: function() {
            return ticked;
        },
        getSteps: function() {
            return steps;
        }
    };
}());

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

    async.parallel([
        function(onDoneCallback) {
        Updater.a(onDoneCallback);},
        function(onDoneCallback) {
        Updater.b(onDoneCallback);},
        function(onDoneCallback) {
        Updater.c(onDoneCallback);}
    ], function(err, results) { // final callback when they're all done
        Updater.tickProgress('All done after ' + Updater.getTicks() + ' ticks.', true);
    });
});
0

精彩评论

暂无评论...
验证码 换一张
取 消