Interesting setTimeout loop Issue in Javascript

I encountered this problem in my dissertation, so I wanted to simulate this problem to get insights. 

Problem 

When I click play button, I want to go through all the steps/iteration(102) until I click the pause button.  If the pause button is clicked, I want to stop at the current iteration(73). Later, If the play button is pressed, I want to resume from the iteration(74) from where I left off.  

Currently, when the pause button is pressed, the setTimeout is not stopping.


Firstly I implemented the two buttons:

sample.html

<button>Run Code
</button>
<button>Pause Code
</button>


Next, I want to execute the respective functions when the button is clicked.

sample.html

<button onclick="mouseup()">Run Code
</button>
<button onclick="pause()">Pause Code
</button>

sample.js

let flag = 0;

function mouseup() {
  console.log("Value of flag " + flag);
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " + flag);
}

Let's add the loop to iterate through all steps while pressing the play button.

let flag = 0;

function mouseup() {
  console.log("Value of flag " + flag);
  let i = 0;
  for (; i < 73; i++) {
    console.log("value i " + i);
    if (flag === 1) {
      clearTimeout(timeout);
      break;
    }
  } //for loop
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " + flag);
}


Before even I navigate to the pause button, the loop ended. So I wanted to delay this iteration like every step has to delay for sometime. This will give me time to press the pause button.

setTimeout 

The setTimeout() method calls a function after a number of milliseconds.

1 second = 1000 milliseconds.                                      

setTimeout is executed only once. It is asynchronous so doesn't wait for it to finish. 

To clear a timeout, use the id returned from setTimeout():

myTimeout = setTimeout (function, milliseconds);                                      

Then you can stop the execution by calling clearTimeout();

clearTimeout(myTimeout);                              

I want to change the for loop to while, so changed accordingly. 

let flag = 0;

function mouseup() {
  console.log("Value of flag " + flag);
  let i = 0;
  while(i < 73) {
    console.log("value i " + i);
    if (flag === 1) {
      clearTimeout(timeout);
      break;
    }
    i++;
  } //for loop
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " + flag);
}

I introduced setTimeout to delay each iteration.

let flag = 0;

function mouseup() {
  console.log("Value of flag " + flag);
  let i = 0;
  while(i < 73) {
    var timeout = setTimeout(function(){
        
       console.log("i " + i + "  " + 
new Date().toLocaleTimeString([],
{ hour: '2-digit', minute: "2-digit", hour12: false })); console.log("flag " + flag + " timeout " + timeout); }, i); if (flag === 1) { clearTimeout(timeout); break; } i++; } //for loop } function pause() { flag = 1; console.log("Value of flag in pause() " + flag); }

Though I have given a function and delay as parameter, the maximum delay my while loop go through is just 75 milliseconds (0.075 seconds) on the last iteration. So the loop still executed fast enough and gave me no time to press the pause button before the loop ends. 

If you see the i value, it is always 73 as the setTimeout executed only once. So let me change it to get the exact iteration number. 

I copied the i value into const j every time. 

Difference between const & let 

const is a signal that the identifier won't be reassigned. `let` is a signal that the variable may be reassigned, such as a counter in a loop, or a value swap in an algorithm.

let flag = 0;
let delay = 300;

function mouseup() {
  console.log("Value of flag " + flag);
  let i = 0;
  while(i < 73) {
    const j = i;
    var timeout = setTimeout(function(){
        

console.log("i " + i + " j " + j + " " +
new Date().toLocaleTimeString([],
{ hour: '2-digit', minute: "2-digit", hour12: false }));

console.log("flag " + flag + " timeout " + timeout); }, i); if (flag === 1) { clearTimeout(timeout); break; } i++; } //for loop } function pause() { flag = 1; console.log("Value of flag in pause() " + flag); }

Now it captures the each iteration count. 


It again executed fast, so I introduced the delay counter globally. 

let flag = 0;
const delay = 300;

function mouseup() {
  console.log("Value of flag " + flag);
  let i = 0;
  while(i < 73) {
    const j = i;
    var timeout = setTimeout(function(){
        
       console.log("i " + i + "  j " + j + "  " + 
            new Date().toLocaleTimeString([], 
            { hour: '2-digit', minute: "2-digit", hour12: false }));
          
       console.log("flag " + flag + " timeout " + timeout);
        }, delay * i);
        
    if (flag === 1) {
      clearTimeout(timeout);
      break;
    }
    i++;
  } //for loop
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " + flag);
}

Output:

1
2
3
4
5
"Value of flag 0"
"i 73  j 0  14:34"
"flag 0 timeout 73"
"i 73  j 1  14:34"
"flag 0 timeout 73"
"i 73  j 2  14:34"
"flag 0 timeout 73"
"i 73  j 3  14:34"
:
:
"flag 0 timeout 73"
"i 73  j 8  14:34"
"flag 0 timeout 73"
"i 73  j 9  14:34"
"flag 0 timeout 73"
"Value of flag in pause()  1" >>>>>>>>>>>>>
"i 73  j 10  14:34"
"flag 1 timeout 73"
"i 73  j 11  14:34"
:
:
"flag 1 timeout 73"
"i 73  j 19  14:34"
"flag 1 timeout 73"
"i 73  j 20  14:34"
"flag 1 timeout 73"
"i 73  j 21  14:34"
"flag 1 timeout 73"

As I want to store the value of  the current step while pressing the pause button, I will have a global variable for the steps instead of i.

let flag = 0;
const delay = 300;
const totalStps = 102;
var curStp = 0;

function mouseup() {
  let i = 0;
  console.log("Value of flag " + flag);
  while(i < totalStps - curStp) {
    const j = i;
    var timeout = setTimeout(function(){
        
       console.log("i " + i + "  j " +
                    j + " curStp " + curStp);
       curStp++;   
       console.log("flag " + flag + " timeout " + timeout);
        }, delay * i);
        
    if (flag === 1) {
       console.log("break the loop");
       clearTimeout(timeout);
       // This is not stopping this setTimeout
       break;
    }
    i++;
  } //while loop
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " 
              + flag + " curStp " + curStp);
}

Output:

1
2
3
4
5
"Value of flag 0"
"i 102  j 0 curStp 0"
"flag 0 timeout 102"
"i 102  j 1 curStp 1"
"flag 0 timeout 102"
"i 102  j 2 curStp 2"
:
:
"flag 0 timeout 102"
"i 102  j 8 curStp 8"
"flag 0 timeout 102"
"Value of flag in pause()  1 curStp 9"
"i 102  j 9 curStp 9"
"flag 1 timeout 102"
"i 102  j 10 curStp 10"
"flag 1 timeout 102"
:
:
"i 102  j 19 curStp 19"
"flag 1 timeout 102"
"i 102  j 20 curStp 20"
"flag 1 timeout 102"
"Value of flag in pause()  1 curStp 21"
"i 102  j 21 curStp 21"
"flag 1 timeout 102"
"i 102  j 22 curStp 22"
"flag 1 timeout 102"
:
:
"flag 1 timeout 102"
"i 102  j 98 curStp 98"
"flag 1 timeout 102"
"i 102  j 99 curStp 99"
"flag 1 timeout 102"
"i 102  j 100 curStp 100"
"flag 1 timeout 102"
"i 102  j 101 curStp 101"
"flag 1 timeout 102"




Final code 

let flag = 0;
const delay = 300;
const totalStps = 102;
var curStp = 0;

function mouseup() {
  let i = 0;
  console.log("Value of flag " + flag);
  while(i < totalStps - curStp) {
    const j = i;
    var timeout = setTimeout(function(){
       let stp = curStp;
       console.log("i " + i + "  j " +
                    j + " curStp " + curStp);
       
       curStp = stp+1;   // this is done by setState.
       console.log("flag " + flag + 
                   " timeout " + timeout);
        }, delay * i);
        
    if (flag === 1) {
       console.log("break the loop");
       clearTimeout(timeout);
       // This is not stopping this setTimeout
       break;
    }
    i++;
  } 
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " 
                + flag + " curStp " + curStp);
  let stp = curStp;
  curStp = stp;   // this is done by setState.
}

SetTimeout problem simulation in Jsfiddle

Though I successfully simulated the issue, I wasn't sure about the setInterval, so I wanted to try with setInterval.

  • setTimeout allows us to run a function once after the interval of time.
  • setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.

Here is the setInterval simulation. 

let flag = 0;
const delay = 300;
const totalStps = 102;
var curStp = 0;

function mouseup() {
  let i = 0;
  console.log("Value of flag " + flag);
  while(i < totalStps - curStp) {
    const j = i;
    (function(i) {
    var timeout = setInterval(function(){
       let stp = curStp;
       console.log("i " + i + "  j " + j + " curStp " + curStp);
       
       curStp = stp+1;   // this is done by setState.
       console.log("flag " + flag + " timeout " + timeout);
        }, delay * i)
    })(i);
    if (flag === 1) {
       console.log("break the loop");
       clearInterval(timeout);
       // This is not stopping this setTimeout
       break;
    }
    i++;
  } 
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " + flag + " curStp " + curStp);
  let stp = curStp;
  curStp = stp;   // this is done by setState.
}

SetInterval problem simulation

Let's try to solve the issue of setTimeout. 

1. At each iteration, I created a new interval ticker. It will never see the changes made by the pause() function because 'if (flag == 1)' executes immediately. We shouldn't use a loop to wait for asynchrnous change. 

2. Though we can still use the while loop to create interval ticker at each iteration, there is a pain staking task of clearing all of them to pause. So, it is simpler not to use the while loop.

3. When the function is called, let it set a timeout again after it is run.

4. This means when toRepeat in the below example runs, it will create a timeout to run again so it runs the next step. Then it runs again and calls setTimeout again and so on until curStp is less than totalStps. 

let flag = 0;
const delay = 300;
const totalStps = 102;
var curStp = 0;
var timeout = null;

function toRepeat() {
    let stp = curStp;
    console.log("curStp = " + curStp);
    
    curStp = stp+1;   
    console.log("flag " + flag + " timeout " + timeout);

    // check for pause
    if(flag === 1) {
      console.log("break the loop");
      console.log('clearing timeout = ', timeout);

      // dont continue because we paused
      return;
    }

    if(curStp != totalStps) {
      // setup another timeout for next step
      timeout = setTimeout(toRepeat, delay);
    } else {
      // we already finished
    }
}

function mouseup() {
  
  flag = 0;
  var timeout = setTimeout(toRepeat, delay);
}

function pause() {
  flag = 1;
  console.log("Value of flag in pause()  " + flag + " curStp " + curStp);
  let stp = curStp;
  curStp = stp;   // this is done by setState.
}

Solution: https://jsfiddle.net/0qrxgbos/


References

const & let

w3schools - setTimeout

SO link for solution

Comments