Why isn't setTimeout cancelling my loop?
Asked Answered
M

6

75

I wondered how many times can a JavaScript while statement (in Chrome's console) can increment a variable in a millisecond, so I quickly wrote this snippet directly into console:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

The problem is that it runs forever.
Why is this happening, and how can I solve it?

Meraree answered 9/2, 2014 at 8:4 Comment(9)
Because the while loop does not yield processor time to the timeoutGarold
Maybe this would be useful to read : #21463877Androsphinx
Why is this question so highly upvoted?Egger
@remyabel I think it's because it's actually a great case for demonstrating how the single-thread works. It's a common assumption with JS that an interval/timeout will interrupt the control flow immediately and execute, when in some cases - as above - this will never happen.Trailer
As an extension to the answers below, this is how you'd do what you expected, using a delayed recursive function: var run = true, i = 0; function loop() { i++; if (run) setTimeout(loop, 0); }; setTimeout(function() { run = false }, 1); loop();Herren
@Herren actually, this would not yield a correct result, as the minimum delay for a timeout is 4ms per definition. So you waste a lot of time in the timeout and function calls.Binion
@Binion It's different per-browser, I think Firefox's minimum is actually either 25ms or 40ms. The example was intended for the idea.Herren
@Herren It's off by a few ms due to clamping, but generally it's not different, since it's defined in the HTML5 spec.Binion
not only that but the tab itself is blocked in Chrome so you can't refresh the webpage or change the URLVillain
E
80

This comes back to the one-threaded nature of JavaScript1. What happens is pretty much this:

  1. Your variables are assigned.
  2. You schedule a function, to set run = false. This is scheduled to be run after the current function is run (or whatever else is currently active).
  3. You have your endless loop and stay inside the current function.
  4. After your endless loop (never), the setTimeout() callback will be executed and run=false.

As you can see, a setTimeout() approach wont work here. You might work around that by checking the time in the while condition, but this will tamper with your actual measurement.

1 At least for more practical purposes you can see it as single-threaded. Actually there is an so called "event-loop". In that loop all functions get queued until they are executed. If you queue up a new function, it is put at the respective position inside that queue. After the current function has finished, the engine takes the next function from the queue (with respect to timings as introduced, e.g., by setTimeout() and executes it.
As a result at every point in time just one function is executed, thus making the execution pretty much single threaded. There are some exceptions for events, which are discussed in the link below.


For reference:

Exponible answered 9/2, 2014 at 8:10 Comment(7)
As @Exponible hinted at, to achieve the kind of effect you were looking for, save the current timestamp in a variable, var start = (new Date()).getTime(); Then in the while statement subtract that time from the current time and check to see if the resulting number is less than your intended delay. while ((new Date()).getTime() - start < delay).Amalita
If all you want is to see how fast Chrome makes it run, then set the while loop to run for something like 10,000 increments. Set a variable like start time before the loop, and then current time after the loop, and then compare the two.Catharine
"Sometimes" is precisely when the stack is empty.Androsphinx
It is not really because of the one-threaded nature of javascript. Even if it would be very stupid, the implementation could be "check for messages between each instruction" after all, as JS is an interpreted language. It is because of the event loop implementation, which is called when the stack is empty.Androsphinx
@Guilro As for the "sometimes" - the concept is called irony and I think I clarified with the highlighted "never". As for the "message between" - every JS engine I know is basically single threaded (except workers). Refer to https://mcmap.net/q/63567/-is-javascript-guaranteed-to-be-single-threaded for a more in depth analysis. Btw most modern engines only fall back to interpreting JS directly, when their more advanced capabilities fail.Exponible
@Exponible You're right, there is probably no engine which check for message between instruction, and they are all single threaded. I just wanted to say the last is a necessary but not sufficient condition. The first must be true too, so better precise it, even if all JS engines actually respect it. Maybe your answer would be more complete if you would add something about the event loop.Androsphinx
@Exponible By the way, the answer to the question you linked actually demonstrates, that is some case, some events are immediately fired between instruction calls, without the stack being empty. So really better precise the stuff about the event loop and setTimeout being depending on it.Androsphinx
P
24

JavaScript is single-threaded so, while you are in the loop, nothing else gets executed.

Puce answered 9/2, 2014 at 8:11 Comment(1)
Be careful when you call JavaScript single-threaded, as it has the ability to handle multithreading. Standard execution, however, is single-threaded.Eartha
C
19

To keep the true speed of Chrome without having to constantly retrieve the time to calculate speed, you could try this JS code:

var start = new Date().getTime()
var i = 0
while(i<1000000)
{
    i++
}
var end = new Date().getTime()
var delta = end-start
var speed = i/delta
console.log(speed + " loops per millisecond")
Catharine answered 9/2, 2014 at 13:29 Comment(0)
A
4

Javascript is single-threaded, that mean it runs only one instruction at a time, sequentially.

The event system, like in many other languages and library, is handle by an event loop. The event loop is basically a loop, which on each iteration check for message in the queue, and dispatch events.

In javascript (as in mot of languages implementing this pattern), the event loop is called when the stack is empty, that is to say, when all functions have returns, in other word, at the end of the program code.

Your "real" program look something like this behind the scene :

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

while(true) {
/*
 * check for new messages in the queue and dispatch
 * events if there are some
 */
  processEvents();
}

So the message from the clock saying timeout is over is never processed.

More info on the event loop on : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop


Of course it is a bit more complex, check here those examples : Is JavaScript guaranteed to be single-threaded? (tl;dr: In some browser engines, some external events are not dependent on the event loop and are immediately fired when they occur, preempting the current task. But this is not the case with setTimeout, which just add a message to the queue and never fires immediately.)

Androsphinx answered 9/2, 2014 at 15:16 Comment(0)
P
2

The While loop doesn't access the setTimeout. You have code that sets run true, and then it will never become false.

Projection answered 9/2, 2014 at 20:6 Comment(0)
A
1

JavaScript has single thread and has single-threaded anywhere.

I think this question in good: Is JavaScript guaranteed to be single-threaded?

When your code is in loop other ocde not execute and block.

Amatol answered 10/2, 2014 at 4:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.