0

I need to perform a huge calculation. So, I refer here to create a simple loading screen. Unfortunately, the loading screen is shown after the calculation complete.

Here is my code

class RosterScheduler
{
   .........................
   autoAssign()
   {
    var startDate=parseInt($("#autoPlannStartDate").val());
    var endDate=parseInt($("#autoPlanEndDate").val());
    $("body").addClass("loading");
    if (startDate>endDate)
        alert("Invalid start date or end date selection");
    else
    {   
        if (this.rosterTable.haveInvalidPreferredShift(startDate,endDate))
            alert("Invalid shift requirement detected");
        else
        {
            var self=this;
            var finalRoster;
            var roster,tempAverageSD,lowestAverageSD=100.0;

            this.theLowestSDRosters=[];
            this.theLowestMissingShiftRosters=[];
            for (var i=0;i<100;i++)
            {   
                this._genRoster(startDate,endDate);
            }
            console.log("Done");
        }
    }   
   } 
}

I found that if I remark the for loop, the loading screen working properly. If I uncomment the for loop, the loading screen is shown after the "Done" is shown in the console. How can I fix the problem? I have tried to convert the function _genRoster into a Promise function, however, it does not work.

The KNVB
  • 3,588
  • 3
  • 29
  • 54
  • just a hack , can you try putting for loop inside a setTimeout. – Atul Aug 16 '18 at 04:32
  • `I have tried to convert the function _genRoster into a Promise function` so you say - but without showing the code *you are having a problem with* it's hard to help – Jaromanda X Aug 16 '18 at 05:28
  • why would you need to run `this._genRoster(startDate,endDate);` 100 times with the same `startDate` and `endDate`? – Jaromanda X Aug 16 '18 at 05:38

1 Answers1

3

It looks like _genRoster is blocking the browser, and not giving it any resources to re-render/repaint until the loop is completed. One possibility would be to run the loop after giving the browser a few ms to render the .loading:

this.theLowestSDRosters = [];
this.theLowestMissingShiftRosters = [];
setTimeout(() => {
  for (var i = 0; i < 100; i++) {
    this._genRoster(startDate, endDate);
  }
  console.log("Done");
}, 50);

It might be more elegant to call a function with 0 timeout after a single requestAnimationFrame, thus giving the browser time to repaint and then running the heavy loop immediately afterwards:

(warning: the following code will block your browser for a bit)

document.body.style.backgroundColor = 'green';
window.requestAnimationFrame(() => {
  console.log('start, setting timeout');
  setTimeout(() => {
    for (let i = 0; i < 1000000000; i++) {
    }
    console.log('done');
  });
});
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • why would you use both RAF and setTimeout? – Jaromanda X Aug 16 '18 at 05:36
  • @JaromandaX The function passed to `requestAnimationFrame` runs *immediately before* the browser's next repaint, not after. I don't know of an event or something that runs immediately *after* a repaint, so using `setTimeout` puts the heavy `for` loop on the stack, so it runs as soon as it can immediately after the repaint. (Doesn't work without the `setTimeout`) Is there a nicer way? – CertainPerformance Aug 16 '18 at 05:40
  • oh right, so you do the intensive stuff just after repaint - smart! – Jaromanda X Aug 16 '18 at 05:47
  • though, I would've thought that RAF would be called well before the next repaint, otherwise intensive (blocking) code would possibly delay the next paint - given the idea of RAF is for "smoother" animation, to me, at least, it would make sense that the RAF callback is called just after repaint (however, your description is correct) – Jaromanda X Aug 16 '18 at 05:54