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

发布于 2024-12-08 11:44:02 字数 724 浏览 0 评论 0原文

假设您想要循环 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/

谢谢!

Suppose you have 3 arrays you want to loop over, with lengths x, y, and z, and for each loop, you want to update a progress bar. For example:

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

However, in this example, the progress bar doesn't update until the function completes. Therefore, I believe I need to use setTimeout to make the progress bar update while the function runs, although I'm not sure how to do that when you have nested for loops.

Do I need to break each loop up into its own function, or can I leave them as nested for loops?

I created a jsfiddle page in case you'd like to run the current function: http://jsfiddle.net/jrenfree/6V4Xp/

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

_失温 2024-12-15 11:44:02

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

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

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

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

The problem with the code in the accepted answer (as of Jun 26 '12) is that it creates a queue of timeout events that don't fire until the triple loop has already exited. You're not actually seeing the progress bar update in real-time, but seeing a late report of what the values of the variables were at the time they were captured in the inner closure.

I'd expect that your 'recursive' solution looks a bit like using continuation-passing style to ensure that your loop doesn't continue until after you've yielded control via setTimeout. You might not know you were using CPS, but if you're using setTimeout to implement a loop, you're probably pretty close to it.

I've spelled out this approach for future reference, because it's useful to know, and the resulting demo performs better than the ones presented. With triple nested loops it looks a bit convoluted, so it may be overkill for your use case, but can be useful in other applications.

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

You can see on jsFiddle that this version updates quite smoothly.

橘亓 2024-12-15 11:44:02

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

现场演示。

If you want to use setTimeout you could capture the x, y, z and count variables into a closure:

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.

永言不败 2024-12-15 11:44:02

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

在这里你可以看到,我尝试在计数等于 0 时记录计数。如果我在 setTimeout 回调函数中执行此操作,则在所有周期后执行该函数,你将得到 100000 没有 0。这解释了为什么进度条仅显示 100%。 js Fiddle 链接到此脚本

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 Fiddle 链接

编辑:
刚刚检查了 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 小提琴

Probably a jquery function in reportprogress plugin uses a setTimeout. For example if you use setTimeout and make it run after 0 milliseconds it doesn't mean that this will be run immediately. The script will be executed when no other javascript is executed.

Here you can see that i try to log count when its equal to 0. If i do it in setTimeout callback function then that is executed after all cycles and you will get 100000 no 0. This explains why progress-bar shows only 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');

wrapping everything after for(i) in setTimeout callback function made the progress-bar work. js Fiddle link

Edit:
Just checked that style setting code for item is actually executed all the time. I think that it might be a browser priority to execute javascript first and then display CSS changes.

I wrote a another example where i replaced first for loop with a setInterval function. It's a bit wrong to use it like this but maybe you can solve this with this 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 Fiddle

谈情不如逗狗 2024-12-15 11:44:02

我根据上次回复找到了解决方案,但将间隔时间更改为 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);
              });

I've found a solution based on the last reply but changing the interval time to one. This solution show a loader while the main thread is doing an intensive task.

Define this function:

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

And call it like this:

       loading( function() {
                // This take long time...
                data.sortColsByLabel(!data.cols.sort.asc);
                data.paint(obj);
              });
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文