在循环多个变量时使用setTimeout更新进度条

时间:2011-10-07 17:00:23

标签: javascript jquery progress-bar settimeout

假设您有3个要循环的数组,长度为x,y和z,并且对于每个循环,您想要更新进度条。例如:

function run() {
    x = 100;
    y = 100;
    z = 10;
    count = 0;
    for (i=0; i<x; i++) {
        //some code
        for (j=0; j<y; j++) {
            // some code
            for (k=0; k<z; k++) {
                //some code
                $("#progressbar").reportprogress(100*++count/(x*y*z));
            }
        }
    }
}

但是,在此示例中,进度条在功能完成之前不会更新。因此,我相信我需要使用setTimeout在函数运行时更新进度条,虽然我不知道如何在嵌套for循环时这样做。

我是否需要将每个循环分解为自己的函数,还是可以将它们作为循环嵌套?

我创建了一个jsfiddle页面,以防您想要运行当前函数:http://jsfiddle.net/jrenfree/6V4Xp/

谢谢!

4 个答案:

答案 0 :(得分:4)

TL; DR:使用CPS:http://jsfiddle.net/christophercurrie/DHqeR/

accepted answer中的代码(截至12月26日)的问题是它创建了一个超时事件队列,在三重循环已经退出之前不会触发。您实际上并没有实时看到进度条更新,而是看到了在内部闭包中捕获它们时变量值的延迟报告。

我希望你的'递归'解决方案看起来有点像使用continuation-passing style来确保你的循环在你通过setTimeout产生控制之后才会继续。您可能不知道您使用的是CPS,但是如果您使用setTimeout来实现循环,那么您可能非常接近它。

我已经详细说明了这种方法以供将来参考,因为知道它很有用,并且得到的演示比所呈现的更好。对于三重嵌套循环,它看起来有点复杂,因此对于您的用例可能有点过分,但在其他应用程序中可能很有用。

(function($){
    function run() {
        var x = 100,
            y = 100,
            z = 10,
            count = 0;

        /*
        This helper function implements a for loop using CPS. 'c' is
        the continuation that the loop runs after completion. Each
        'body' function must take a continuation parameter that it
        runs after doing its work; failure to run the continuation
        will prevent the loop from completing.
        */
        function foreach(init, max, body, c) {
            doLoop(init);
            function doLoop(i) {
                if (i < max) {
                    body(function(){doLoop(i+1);});
                }
                else {
                    c();
                }
            }
        }

        /*
        Note that each loop body has is own continuation parameter (named 'cx',
        'cy', and 'cz', for clarity). Each loop passes the continuation of the
        outer loop as the termination continuation for the inner loop.
        */
        foreach(0, x, function(cx) {
            foreach(0, y, function(cy) {
                foreach(0, z, function(cz) {
                    count += 1;
                    $('#progressbar').reportprogress((100*(count))/(x*y*z));
                    if (count * 100 % (x*y*z) === 0) {
                        /*
                        This is where the magic happens. It yields
                        control to the javascript event loop, which calls
                        the "next step of the foreach" continuation after
                        allowing UI updates. This is only done every 100
                        iterations because setTimeout can actually take a lot
                        longer than the specified 1 ms. Tune the iterations
                        for your specific use case.                   
                        */
                        setTimeout(cz, 1);
                    } else {
                        cz();
                    }
                }, cy);
            }, cx);
        }, function () {});    
    }

    $('#start').click(run);
})(jQuery);

您可以在jsFiddle上看到此版本更新非常顺利。

答案 1 :(得分:1)

如果要使用setTimeout,可以将x,y,z和count变量捕获到闭包中:

function run() {
    var x = 100,
        y = 100,
        z = 10,
        count = 0;
    for (var i=0; i<x; i++) {
        for (var j=0; j<y; j++) {
            for (var k=0; k<z; k++) {
                (function(x, y, z, count) {
                    window.setTimeout(function() {
                        $('#progressbar').reportprogress((100*count)/(x*y*z));
                    }, 100);
                })(x, y, z, ++count);
            }
        }
    }
}

Live demo

答案 2 :(得分:1)

reportprogress插件中的jquery函数可能使用setTimeout。例如,如果您使用setTimeout并使其在0毫秒后运行,则并不意味着它将立即运行。当没有其他javascript被执行时,脚本将被执行。

在这里你可以看到我尝试在等于0时尝试记录计数。如果我在setTimeout回调函数中执行它,那么在所有周期后执行并且你将得到100000没有0.这解释了为什么进度条只显示100%。 js Fiddle link to this script

function run() {
    x = 100;
    y = 100;
    z = 10;
    count = 0;
    for (i=0; i<x; i++) {
        //some code
        for (j=0; j<y; j++) {
            // some code
            for (k=0; k<z; k++) {
                //some code
                if(count===0) {
                     console.log('log emidiatelly ' + count);
                    setTimeout(function(){
                        console.log('log delayed ' + count);
                    },0);
                }
                count++;
            }
        }
    }
}
console.log('started');
run();
console.log('finished');

在(i)setTimeout回调函数之后包装所有内容使得进度条工作。 js Fiddle link

修改 刚刚检查了项目的样式设置代码是否一直在执行。我认为首先执行javascript然后显示CSS更改可能是浏览器的优先级。

我写了另一个例子,我用setInterval函数替换了第一个for循环。像这样使用它有点不对,但也许你可以用这个黑客解决这个问题。

var i=0;
var interval_i = setInterval(function (){

    for (j=0; j<y; j++) {
        for (k=0; k<z; k++) {
            $("#progressbar").reportprogress(100*++count/(x*y*z));
        }
    }

  i++;
  if((i<x)===false) {
    clearInterval(interval_i);
  }
},0);

JS Fiddle

答案 3 :(得分:0)

我找到了一个基于上一个回复的解决方案,但是将间隔时间更改为一个。该解决方案显示了一个加载器,而主线程正在执行密集任务。

定义此功能:

       loading = function( runme ) {
                    $('div.loader').show();
                    var interval = window.setInterval( function() { 
                          runme.call();  
                          $('div.loader').hide();
                          window.clearInterval(interval);
                    }, 1 );
               };

并称之为:

       loading( function() {
                // This take long time...
                data.sortColsByLabel(!data.cols.sort.asc);
                data.paint(obj);
              });