多个异步回调共享变量的作用域问题

4
我正在使用 Caolan McMahon 的异步库 async.js library 和 jQueryUI 进度条progress bar 为用户提供反馈,同时多个异步调用收集数据并填充复杂图表的元素。
我的问题是:最好的方法是什么,可以让异步方法共享数据?
这是我所做的简化示例。使用全局变量已经可以工作,但它们会让我感到不安,并且让 jsLint 发出警告。将参数传递或将其限定在文档准备函数中会导致它失败。
在我的真实代码中,updateA() 等对应的代码有数百行,包括 XHR 调用。
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:

如果您想在那里尝试,请使用JSFiddle上的示例代码

3个回答

5

通常来说,创建自己的closuredFunction-Context'ed "region"总是一个好主意。你可以通过在应用程序周围包装一个自我调用匿名函数来实现这一点。代码可能如下所示:

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

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

这样,您就永远不会破坏全局命名空间。当然,有时候您需要一个全局对象,但除非绝对必要,否则您真的应该尽量避免使用它。


3
您可以将全局变量放入“状态”对象中,并将其传递给所有回调函数。例如:
$(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);
      }
    );
});

您需要将updateA、updateB、updateC和tickProgress更改为使用提供的状态对象,而不是全局变量。


谢谢你的回答,@kirilloid,但是我不喜欢这种方法所需的过多参数传递,它与拥有全局对象一样。 - Nathan

2
David在JS聊天室中向我介绍了这种方法(前几天)。它利用了全局对象,但我更喜欢对象内部方法的逻辑封装,而不是jAndykirilloid的建议。 在jsfiddle上看它运行。
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);
    });
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接