2011-10-07 82 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次迭代的性能更好。 –