2

I'm creating a progam, where JavaScript is processing a huge amount of data, so I want to show the progress on a progressbar.

The problem comes here: While the for loop is running, the progressbar does not update, and then it will be fullfilled immediately.

document.getElementById("start").addEventListener("click",function(){
    max=1000000
    document.getElementById("pb").max=max
    for(i=0;i<max;i++){
        document.getElementById("pb").value=i+1
    }
    console.log("Finished")
})
<progress id="pb" value="0" max="0"></progress>
<input type="button" id="start" value="Start"/>

How can I solve that problem?

I don't want to use any JS library, if it's possible without them.

Thanks for any help!

FZs
  • 16,581
  • 13
  • 41
  • 50
  • Your blocking the Javascript UI thread, so it never gets chance to update the UI. You need to do this asynchronously. – Keith Dec 17 '18 at 16:36
  • @Keith And how can I do it as async? – FZs Dec 17 '18 at 16:37
  • 1
    Possible duplicate of [Javascript progress bar not updating 'on the fly', but all-at-once once process finished?](https://stackoverflow.com/questions/5743428/javascript-progress-bar-not-updating-on-the-fly-but-all-at-once-once-process) – Herohtar Dec 17 '18 at 16:38
  • What is it your using the progress bar for, as updating the UI at say 60 fps, doing a million UI updates is going to take over 4 and half hours. – Keith Dec 17 '18 at 16:47

2 Answers2

2

You can use requestAnimationFrame:

document.getElementById("start").addEventListener("click",function(){
    var max=100;
    var p=0;
    document.getElementById("pb").max=max;
    var update = function () {
      document.getElementById("pb").value=p; 
      p++;
      if (p <= max) requestAnimationFrame(update);
      else console.log("Finished");
    };
    update();
})
<progress id="pb" value="0" max="0"></progress>
<input type="button" id="start" value="Start"/>

Or just use setTimeout and increase the timer:

document.getElementById("start").addEventListener("click",function(){
    max=100;
    document.getElementById("pb").max=max;
    for(i=0;i<max;i++){
        setTimeout(function () {
          document.getElementById("pb").value=this;
          if (this == max) console.log("Finished")
        }.bind(i+1), i*16);
    }
})
<progress id="pb" value="0" max="0"></progress>
<input type="button" id="start" value="Start"/>
rafaelcastrocouto
  • 11,781
  • 3
  • 38
  • 63
1

Here is a way of doing this async / await way,.. So you can keep your for loop.

Also notices I've reduced the max to 1000, as waiting 4 and half hours might be overkill. :)

function sleep(ms) { return new Promise((r) => 
  setTimeout(r, ms)); }
  
async function run() {
  let max=1000;
  document.getElementById("pb").max=max;
  for(let i=0;i<max;i++){
    document.getElementById("pb").value=i+1
    await sleep(0);
  }
  console.log("Finished")
}

document.getElementById("start").
  addEventListener("click", run);
<progress id="pb" value="0" max="0"></progress>
<input type="button" id="start" value="Start"/>

Update, the above snippet was deliberately not optimized to keep things simple. But one issue with the above the sleep(0) is called way more often than required, as such I've created a slightly more complex example that processes some CPU intensive task, and how to handle this.

function sleep(ms) { return new Promise((r) => 
  setTimeout(r, ms)); }
  
function doSomeWork() {
  //wait about 5ms
  const st = Date.now();
  while (Date.now() - st < 5);
}
  
async function run() {
  let max = 1000;
  let lTm = Date.now();
  const pb = document.getElementById("pb");
  document.getElementById("pb").max=max;
  for(let i=0;i<max;i++){
    doSomeWork();
    const tm = Date.now();
    if (tm - lTm > 100 || i === max -1) {
      pb.value=i+1;
      await sleep(0);
    }
  }
  console.log("Finished")
}

document.getElementById("start").
  addEventListener("click", run);
<progress id="pb" value="0" max="0"></progress>
<input type="button" id="start" value="Start"/>
Keith
  • 22,005
  • 2
  • 27
  • 44
  • Note that there is a 4ms limit to setTimeout https://stackoverflow.com/questions/9647215/what-is-minimum-millisecond-value-of-settimeout – rafaelcastrocouto Dec 17 '18 at 18:48
  • 1
    @rafaelcastrocouto Yeah, but for a progress bar it should be plenty. – Keith Dec 18 '18 at 02:02
  • Yes it is, you can set it to 16ms and your code will still be in the 60fps limit. – rafaelcastrocouto Dec 18 '18 at 11:52
  • why does this code slow down while running? Each loop takes longer than previous. – jumpjack Sep 20 '22 at 13:14
  • @jumpjack Not sure what you mean,. PS: This example is not meant to be optimal, it's updating the DOM a thousand times when it's not really required. So maybe depending on what browser you using there is a lot of GC going on causing slowdown. To improve, what I could have done, is do a time difference between iterations and only `sleep(0)` maybe after 100ms or so has passed, this would then allow the GUI to still update without issues. But the snippet here was deliberately left simple for others to follow. – Keith Sep 20 '22 at 13:27
  • I have 180 loops to do; first 30 are completed in 1 second, but it takes 1 minute to complete all the loops! Anyway I switched to mapping my JSON object to an array, and this makes things 1000x faster, not requiring progress bar anymore. – jumpjack Sep 20 '22 at 18:09
  • 1
    @jumpjack Like I said the progress bar here is not optimized, deliberately so, otherwise they would not be any progress bar to see as it would be instant. Saying this, I've added another snippet that is more realistic and does the intermittent GUI update, so if you do need a PB in the future, the second snippet would be the better option. – Keith Sep 21 '22 at 09:52