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

Using setTimeout to update progress bar when looping over multiple variables

本文关键字:更新 setTimeout 使用 循环 变量      更新时间:2023-09-26

假设您有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循环时如何做到这一点。

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

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

谢谢!

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

可接受答案中的代码的问题(截至2012年6月26日)是,它创建了一个超时事件队列,直到三重循环已经退出才触发。实际上,您并没有看到进度条实时更新,而是看到了在内部闭包中捕获变量时变量值的最新报告。

我希望你的"递归"解决方案看起来有点像使用延续传递风格,以确保你的循环不会继续,直到你通过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上看到这个版本的更新非常顺利。

如果你想使用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);
            }
        }
    }
}

现场演示。

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

在这里你可以看到,我尝试记录计数,当它等于0。如果我在setTimeout回调函数中这样做,那么在所有周期后执行,你将得到100000没有0。这就解释了为什么进度条只显示100%。链接到这个脚本

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');

在setTimeout回调函数中包装for(i)之后的所有内容使进度条工作。js链接

编辑:

刚刚检查了item的样式设置代码实际上一直在执行。我认为这可能是一个浏览器优先执行javascript,然后显示CSS的变化。

我写了另一个例子,其中我用setInterval函数替换了第一个for循环。这样使用它有点错误,但也许你可以用这个hack来解决这个问题。

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小提琴

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

定义这个函数:

       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);
              });