4

I have this simple loop from 1-20. What I'm trying to do is to stop the loop using a button click. What I did is, I put a condition that upon button click the value of the variable stop will be changed to 1, which will trigger the break. But the value is not changed.

var stop = 0;
for(let i = 1; i <= 20; i++){
  
  if(stop === 1){
    break;
  }
  
  setTimeout(function(){
    $('ul').append('<li>'+ i +'</li>');
  },i * 500);
}

$('button').click(function(){
    stop = 1;
});
ul li{
  list-style-type: none;
  float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul></ul>

<br>
<button>stop</button>

6
  • because your setTimeout are all triggered already Commented Aug 30, 2018 at 8:04
  • add a console.log(i) before your setTimeout you'll see what I mean Commented Aug 30, 2018 at 8:06
  • if I change my if statement to this if (i === 3) { break; } it work. Commented Aug 30, 2018 at 8:06
  • Yes because then you're breaking the loop on 3 Commented Aug 30, 2018 at 8:06
  • That's what I'm trying to do break the loop Commented Aug 30, 2018 at 8:12

5 Answers 5

6

The 20 setTimeout functions were called before you even pressed on stop.
One of many ways to fix this is to check the stop variable inside the function setTimeout is executing.

var stop = 0;
for (let i = 1; i <= 20; i++){
  setTimeout(function(){
    console.log(stop);
    if (stop !== 1) {
      $('ul').append('<li>'+ i +'</li>');
    }
  },i * 500);
}

$('button').click(function(){
    stop = 1;
});
ul li{
  list-style-type: none;
  float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul></ul>

<br>
<button>stop</button>


Edit:

You ask how to stop the for loop. The way it's written you can't.
This is how I would have implemented this in order to "stop" the loop with setTimeout.
You can also do it with setInterval (check Сергей Петрашко's answer to see a how).
Read about the differences between them here):

var stop = false;

function addNumberAndCallNext(number, max) {
    if (!stop && number <= max) {
        $('ul').append('<li>'+ number +'</li>');
        setTimeout(addNumberAndCallNext.bind(null, ++number, max), 500);
    }
}

addNumberAndCallNext(1, 20);

$('button').click(function(){
    stop = true;
});
ul li {
  list-style-type: none;
  float: left;
  padding-right: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul></ul>

<br>
<button>stop</button>

Sign up to request clarification or add additional context in comments.

6 Comments

the variable still doesn't change.
Are you referring to the stop variable? I added console.log(stop) right before the if check. You can see it changes to 1. Of course, the functions are still executing because we set their timeout right at the beginning.
yes sir, you're right. I'm just wondering why the value of stop, doesn't change.
I'm afraid I don't understand. It changes if you press on the stop button. If it did not change you would still see new numbers appearing.
And by all means, don't call me sir, I work for a living :)
|
2

You can try next code without jQuery. I don't recomend to use --- $('ul').append('<li>'+ i +'</li>'); - is bad practice. true way --is create node before loop. in this task you can use setIntrval instead of setTimout and loop

let itemList = (i) => {
    let item = document.createElement('li');
    item.value = i;
    item.textContent = i;
    return item;
}

let list = document.getElementById('list');
let stopButton = document.getElementById('stop');
let i = 0,
    interval;
let reset = () => {
    clearInterval(interval)
}

stopButton.addEventListener('click', () => {
    reset()
});

interval = setInterval(() => {
    list.append(itemList(i))
    i++;
    if (i > 20) reset()
}, 300)
<button id="stop">stop</button>
<br>

<ul id="list"></ul>

1 Comment

You're missing that the delay the items are added is increasing with the count of the items.
2

You can't stop a for loop by a click event because JavaScript is single threaded. The loop beginning

for(let i = 1; i <= 20; i++){
...

runs synchronously when executed without being interrupted by event handlers.

It will initiate 20 timer calls to be executed 500ms apart and return to the event loop before a click handler executes.

You have options to initiate the timer calls anyway and check the stop flag before adding an element, or set up some kind of asynchronous loop (e.g using setInterval) that increments the i variable and stops the timer callback if i reaches a maximum or the click event occurs.

Comments

1

The issue is that the entire for loop has executed before you even had a chance at pressing the button. setTimeout doesn't actually sleep or block, but rather puts a new event and just continues in the for loop.

The browser, all its clocks for timeout purposes and so on can only run when your code has finished running for the time being. There is no thing equivalent to the sleep() function.

Maybe using async functions (ES2015 IIRC, should be supported by all current non-IE browsers), and awaiting for a promise created by util.promisify(setTimeout)(2000) can be more intuitive.

There are no blocking functions. There is no sleeping inside code. All code basically runs on the CPU and sets up I/O and timer events which happen asynchronously, once the synchronous part has already finished.

JavaScript is unintuitive for this reason. However you may feel the value for asynchronicity once you have seen that you can get one kind performance this way. I mean... While JS code itself is executing the DOM cannot be checked by the browser. You cannot scroll, you cannot click, the mouse cursor itself won't change its kind. Only once a code block has finished will the browser handle all the pending events.

Comments

1

I would do this recursive. Then you have two break conditions stop and count === max.

var stop = false;

var print = function(count, max) {
   setTimeout(function(){
      if(stop){
         return;
      }else if(count === max){
         return;
      }else{
         $('ul').append('<li>'+ count +'</li>');
         return print(++count, max);
      }
   }, 500);
}

$('button').click(function(){
    stop = true;
});

print(0, 20);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul></ul>

<br>
<button>stop</button>

4 Comments

It should be just 500 not count * 500
@MaorRefaeli not really
In your answer, every number waits 500ms multiply by its previous number. Number 2 will apear 500ms after number 1 and 11 will apear 5000ms after 10. In the question each number appeared 500ms after its previous. It still works but it has different functionality
@MaorRefaeli You're totally right my fault. Thanks for pointing that out!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.