2011-10-07 41 views
2

假设您有3个要循环遍历的数组,长度为x,y和z,并且对于每个循环,您想要更新进度条。例如:使用setTimeout在循环多个变量时更新进度栏

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来更新进度条,尽管当你嵌套循环时我不知道该怎么做。

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

我的情况下,创建了一个页面的jsfiddle你想运行当前功能:http://jsfiddle.net/jrenfree/6V4Xp/

谢谢!

回答

1

如果你想使用setTimeout的,你可以捕捉到X,Y,Z和计数变量为封闭:

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

+0

进度条更新似乎不是非常流畅。它会跳到百分之几(2或3%),然后停下来,然后突然达到100%。任何想法为什么这样做? – Josiah

+0

我确实需要使用setTimeout,但不是使用闭包,而是使主计算函数递归。我开始意识到setTimeouts不会暂停处理其余的代码,但是您可以使用递归函数似乎强制setTimeouts运行。 – Josiah

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

包裹在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); 

​​

+0

我试过在我的代码中实现这一点,但有些东西没有工作。我猜这是由于其他处理线,其中许多访问索引变量i,j和k – Josiah

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

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

与在accepted answer代码中的问题(如)是它创建了一个超时事件队列,在三重循环已经退出之前不会触发。您实际上并未实时看到进度栏更新,但看到了一个延迟报告,说明在内部关闭中捕获变量时的值。

我希望你的'递归'解决方案看起来有点像使用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上看到这个版本更新很顺利。

+0

这工作正常,但它为什么这么慢?对于x = 10000,y = 1,z = 1(如此迭代10000次),运行需要大约50秒。有什么方法可以加速吗? – Chin

+1

当然,你可以减少产量;即使对于1 ms的'setTimeout'请求,我也看到延迟高达150毫秒,累计超过10000次迭代。只需确定您的用户界面实际需要更新的频率。在这种情况下,你可能只关心更新时百分比的变化,所以最内层循环的最后一行可以替换为:'if(count * 100%(x * y * z)=== 0){setTimeout( cz,1); } else cz();' –

+0

我已经更新了jsFiddle示例,如上面的注释中所示,并且10000次迭代的性能更好。 –