1

I'm new to jQuery, and I have been playing around with it a bit. I have one UI element called result, which is updated once the result arrives and this works perfectly. However, this result comes from a series of computations.
Now I want the UI element to update after each computation to show some sort of progress. However, if I set the value of the UI element inside this recursive function I'm using for computation, it doesn't update it. Here's the code:

function getRes(val) {
  if(val === finalVal)
    return;
  // do something 
  $("#result").text(someVal); //This doesn't work
  getRes(val+1);
}

$("#resbtn").on('click', function(){
  getRes(0);
  $("#result").text(res);  //This works
});

Why is this happening?

Zeokav
  • 1,653
  • 4
  • 14
  • 29

3 Answers3

0

You need to trick the browser into refreshing the DOM. See Why is setTimeout(fn, 0) sometimes useful?

function getRes(val) {
    if(val === finalVal)
    return;
    // do something

    setTimeout(function() {
        $("#result").text(someVal); //This doesn't work
        getRes(val+1);
    }, 0);
}

$("#resbtn").on('click', function(){
    getRes(0);
    $("#result").text(res);  //This works
});
Community
  • 1
  • 1
plarner7
  • 125
  • 6
0

Your code works fine. I logged into another div each pass into your recursive function :

var finalVal = 100;
var someVal;

function resetLogs() {
  $("#log").html('<h3>Logs</h3>');
}

function getRes(val) {
  if(val === finalVal)
    return;
  // do something 
  someVal = "Loading : "+val+'%';
  $("#log").append(someVal+'<br>');
  $("#result").text(someVal);
  getRes(val+1);
}

$("#resbtn").on('click', function(){
  resetLogs()
  getRes(0);
  $("#result").text("FINAL");
});

$("#resbtn2").on('click', function(){
  resetLogs()
  getRes(0);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result">initial content</div>
<button id="resbtn">With 'final'</button> <button id="resbtn2">Without final!</button>

<div id="log">
  <h3>Logs</h3>
</div>

The problem may be that you go trough your recursive function so fast that the displayed data haven't enough time to be properly displayed and is quickly replaced by the final value to display (here "FINAL" string)

Sir McPotato
  • 899
  • 7
  • 21
  • Due to single-threading, the UI will only be updated *after* the onclick handling has finished - and not *during* the recursive calls. To see this effect, replace `//do something` by a time-consuming call like `for(let i=0; i < 1000*1000*100; i++) { i*i*i*i }`. – JimiLoe Jul 16 '22 at 09:18
0

JavaScript is single-threaded (unless you use Web Workers). I.e. the UI is updated after your long-running onclick handler has finished. Using setTimeout() as proposed in the answer by @plarner7 is a typical solution. However, this approach is not well-suited for recursive functions because

  • it changes the control flow of your recursive function and
  • you cannot (easily) access the result of a call that was "spawned" with setTimeout().

The trick is to use setTimeout but instead of shifting logic into the setTimeout call we just call SetTimeout to give the UI a chance to update. The general pattern for recursive functions with UI update is:

  • define a sleep function that behaves similarly to Java's Thread.sleep():

async function sleep(msec) { return new Promise(resolve => setTimeout(resolve, msec)); }

  • insert await sleep(0) after UI changes
  • declare your recursive function as async function - otherwise you will not be allowed to call the async sleep function.

I.e. a solution for the posted question looks like this:

async function sleep(msec) {
   return new Promise(resolve => setTimeout(resolve, msec));
}

async function getRes(val) {
   if(val === finalVal) return;

   // Simulates your time-consuming operation 
   for(let i=0; i < 1000*1000*1000; i++) { i*i*i*i }

   let progress = `${Math.floor(val*100 / finalVal)}%`;  
   $("#result").text(progress);

   // THIS LINE DOES THE TRICK and gives the UI a chance to update
   await sleep(0);

   await getRes(val+1);
}

$("#resbtn").on('click', async () => getRes(0));
JimiLoe
  • 950
  • 2
  • 14
  • 22