11

This is my test code (fiddle here):

console.log('Before wait');
setTimeout(function () { console.log('Yo!'); }, 1000);
var start = Date.now();
while (Date.now() < start + 3000) {}
console.log('After wait');

This is the timeline of events in Chrome:

  • Time 0 seconds: Prints "Before wait"
  • Time 3 seconds: Prints "After wait", and then immediately after "Yo!"

Is this behaviour according to spec? Why is it not

  • Time 0 seconds: Prints "Before wait"
  • Time 3 seconds: Prints "After wait"
  • Time 4 seoncds: Prints "Yo!"

?

5 Answers 5

54

JavaScript is single-threaded. If some block of code uses execution thread, no other code can be executed. This means your setTimeout() call must wait until main execution (the one with busy-waiting while loop) finishes.

Here is what happens: you schedule setTimeout() to execute after a second and then block main thread for 3 seconds. This means the moment your busy loop finishes, timeout is already 2 seconds too late - and JS engine tries to keep up by calling your timeout as soon as possible - that is, immediately.

In fact this:

while (Date.now() < start + 3000) {}

is one of the worst things to do in JavaScript. You hold JavaScript execution thread for 3 seconds and no other event/callback can be executed. Typically browsers "freeze" in that period of time.

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

4 Comments

The fact that JavaScript is single-threaded explains why "Yo!" is printed after "After wait", that's all.
@Randomblue: you are right, I missed that from your question. Updated my answer.
@Randomblue, this is the best answer (so far) though has less upvotes. Weird.
I think this is also a good answer - it does precisely describe the sequence of events, but doesn't directly answer the question as to why the "Yo!" isn't printed after 4 seconds instead of 3 or 1 second. To me, it seems printing "Yo!" after 4 seconds is not logical.
11

The delay of setTimeout is relative to the exact point in time when it is called. It expires while you are still busy waiting. So it will be performed at the next instant where the control goes back into the event loop.

Edit:

The spec is a bit vague in this point, but I guess it's the intended and only straightforward interpretation:

setTimeout(function, milliseconds)

This method calls the function once after a specified number of milliseconds elapses, until canceled by a call to clearTimeout. The methods returns a timerID which may be used in a subsequent call to clearTimeout to cancel the interval.

3 Comments

Do you have specs to back this?
Edit above - added excerpt from the spec.
It seems obvious to me that the function passed to "setTimeout" will be executed as close as possible to the specified number of milliseconds relative to when you called the function - in the question it seems like you would think it executes 1 second after the current thread exits - but that doesn't seem logical to me at all.
2

When you run the busy-waiting loop after the setTimeout call, you don't let time for your "Yo!" to print out, because the Javascript runtime is busy with your loop (actualy the empty statement also makes it busy because of continues evaulaation of the loop condition).

You should always avoid such a busy-waiting loop, because until that finishes, nothing else can be called or run in that window.

2 Comments

'After wait' will always win.
Thank you for the correction, I will update the answer. Anyway the rest may be true I think. And that's not even related to javascript, but to quite a lot of environments.
0

Not sure if it could help you but this problem's been explained in: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Run-to-completion

For this reason, the second argument indicates a minimum time—not a guaranteed time.

Comments

0

The line setTimeout(function () { console.log('Yo!'); }, 1000); will result in creating a timer for 1000ms using the browser API, which will later add the callback function into what is called a callback queue after 1000ms have passed by(this will happen somewhere in the future). However this does not mean that the thread of execution will stop executing the rest of the code. (Since JS in single threaded the browser will handle the waiting part and JS will continue on executing)
while (Date.now() < start + 3000) {} When the thread of execution reaches this line, it will keep checking the condition until it becomes false that means there is no waiting done by the browser API but the thread of execution will keep running the loop for 3 seconds. Somewhere during these 3 seconds the browser API's timer set with the setTimeout function will complete (in approximately 1000ms) and add the function definition of the anonymous function(callback) into the callback queue(This means the countdown of 1000ms has already been completed).
Note: Just because some code that is ready for execution is in the callback queue does not mean it will be executed immediately. It will need to pass two conditions(there are more, but only 2 will be applicable here) to be added to the main call stack which are checked by the event loop. The conditions are:

  1. Call stack must be empty (no functions are currently running)
  2. No more synchronous code left for execution

so in the question provided,
while (Date.now() < start + 3000) {}
console.log('After wait');

both these lines are synchronous code, thus will complete execution before console.log('Yo!'); is added to the call stack and executed.

To sum up the reason "Yo!" is logged immediately after "After wait" is because the two wait times are happening in parallel (with the help of the browser as JS is single threaded)

References: https://www.youtube.com/watch?v=i9lNaTQ6n9c

Comments

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.