I am new to javascript and I am curious about a piece of code and why it is behaving differently in different browsers. This is the piece of code that I have.
function waitThreeSeconds() {
var ms = 3000 + new Date().getTime();
while(new Date() < ms) {}
console.log('Finished function!')
}
function clickHandler() {
console.log('Clicked event!')
}
document.addEventListener('click', clickHandler)
waitThreeSeconds();
console.log('Finished execution!')
As of now, I know that javascript is synchronous and single-threaded. Whenever some sort of events occur javascript puts it in the event queue and waits for the execution stack to be empty. Once the execution stack is empty then it looks into the event queue and processes the events one after another. Even after finishing all the event processing javascript still looks in the event queue for further events to appear.
Keeping this in mind, if I run this code and don't click in the window during the three seconds of delay, the output is
Finished function!
Finished execution!
and after the code executed if I clicked in the window, depending on the number of times I clicked the output becomes,
Finished function!
Finished execution!
Clicked event!
Clicked event!
...
...
Now, this is what I was expecting.
How ever, if I run the code and click in the window during the three seconds of delay, depending on the number of times I clicked the output that I expect is
Finished function!
Finished execution!
Clicked event!
Clicked event!
...
...
and it is happing for Google Chrome. However, if I do the same thing for Brave browser the output becomes
Finished function!
Finished execution!
Though I have clicked the window the output seems to look like I never clicked the window which is confusing for me.
However, after completion of execution, the code works the same way as it should be in both the browsers.
Can anybody help me to understand this behavior?
I just tried to run the code in different browser Chrome and Brave,the result looks same.
Brave
version: 1.11.104 Chromium: 84.0.4147.105
Chrome
version: 84.0.4147.105
Related
I am following https://www.youtube.com/watch?v=Bv_5Zv5c-Ts, and it is explained there that the event loop of JS engine places events in Event Queue - their handlers will be executed only when the Execution Stack is empty. The author even shows an example of it at 1:45:18. The code is:
function waitThreeSeconds() {
var ms = 3000 + new Date().getTime();
while(new Date() < ms) {}
console.log('finished function');
}
function clickHandler() {
console.log('click event!')
}
document.addEventListener('click', clickHandler);
waitThreeSeconds()
console.log('finished execution')
When I run it in the browser, the while loop runs for 3 seconds as expected, and then the 2 messages get printed:
If I click anywhere AFTER the while loop finishes, a message "click event!" gets printed. However, if I click DURING the while loop, the click event is never registered. In the video, in both situations, the click is registered.
Is it due to some updates in the JavaScript engines that happened since the video's premiere in 2015? I'm using the MS Edge browser.
The video's author suggests that even though JS is a single-threaded language, the web browsers implement JS interprets/engines in a concurrent way where the events get added to the Event Queue separately from the JS code's execution.
My experiment confused me since it shows different behavior. Could someone explain why is that?
//EDIT
After further experimentation, I found out that the behavior seen in the video is also to be found in Chromium (and I guess Chrome as well, although I do not have it installed).
Edge, however (the Chromium-based one), behaves differently and does not register the click at all during the while loop.
Web browsers can only operate asynchronously, not technically concurrently. What you've implemented here is what is traditionally called a "busy-wait" loop. This method is a blocking algorithm, meaning that the later code will not execute until the first code is done. This is a good thing though. Imagine how confusing it would be if your code just executed out of order.
If you want to utilize the browser's builtin asynchonous capabilities, you'll need to use one of the functions provided to interact with Javascript's Event Loop.
In this case, you would likely want to use setTimeout() to actually make this properly asynchronous.
function waitThreeSeconds() {
setTimeout(function() {
console.log('finished function');
}, 3000);
}
function clickHandler() {
console.log('click event!')
}
document.addEventListener('click', clickHandler);
waitThreeSeconds()
console.log('finished execution')
Other similar functions are setImmediate(), which executes as soon as the stack is empty, and setInterval(), which allows you to execute a function several times at a regular period or delay.
JavaScript doesn't run concurrently in a browser tab, so whenever you run a for/while loop the thread gets blocked and it must be complete to be able to handle other events in this case your event listeners.
So when you run the while loop no event will get processed until the loop finishes.
I tried running in chrome and it works the same way as it should both the listeners get fired after the loop.
The while loop is blocking, and will prevent the event queue from progressing. There is a similar question here! A good approach is to replace the while loop with a setTimeout(function(){},3000) or setInterval(function(){},3000)
If you use a while loop, you will be blocked from doing anything for those 3 seconds. Instead, you need to use timeout. This will allow you to continue to do things, while the timeout is running.
let activeTimer = null;
function waitThreeSeconds() {
if (activeTimer) {
console.log('stopped function execution');
clearTimeout(activeTimer);
}
console.log('began function execution');
activeTimer = setTimeout(function() {
console.log('finished function function');
activeTimer = null;
}, 3000);
}
function nonBlockingClickHandler() {
console.log(`Click event <${activeTimer != null}>!`);
}
document.addEventListener('click', nonBlockingClickHandler);
waitThreeSeconds();
lets imagine a scenario
function clickEventCheck() {
document.querySelector('#some-id').addEventListener('click', () => {
console.log("The button is clicked");
});
}
clickEventCheck();
Now I get that for the first time the 'clickEventCheck' function is getting called and we can handle the click event.
But now the execution stack is empty so how the event handler line of code which is inside our function(which has returned) still gets executed every time we click the button?
When the function is called click listener is added to the element with id some-id. So, whenever you click the element, the code inside the callback function gets executed.
Until and unless the click listener is not explicitly removed from the element i:e element with id some-id, it will listen to click events.
function clickHandler() {
console.log('The button is clicked');
}
function clickEventCheck() {
document.querySelector('#some-id').addEventListener('click', clickHandler);
}
clickEventCheck();
document.querySelector('.remove').addEventListener('click', () => {
document.querySelector('#some-id').removeEventListener('click', clickHandler);
});
<button id="some-id">Click Me</button>
<button class="remove">Remove Click Listener</button>
JavaScript is doing more than just executing your code sequentially, line by line. In the background there is also something running called the event loop. This frequently checks another stack to see if any other instructions are there and if so runs those, starting additional chains of execution called frames.
Two nice properties of those frames is that they will keep running as long as they can so you don't have to reason about random switching between them, and they don't block, so once a frame can't execute any further, the engine will look for another frame to execute from the stack. This also leads to smooth switching between frames provided you are not doing CPU-heavy execution (which isn't normally what JavaScript's used for).
Practically what might happen is one frame (frame A) makes an I/O request, and provides a callback function when it does so. Frame A then runs out of code to execute and the event loop picks the next frame (frame B) to run. In the background, when the I/O request completes the callback function will be added to the stack—and then when frame B runs out of code, the event loop can pick up that callback and execute it in yet another frame. The same process could apply for a button click, a mouse move, or any asynchronous process you can get the computer to do.
In this way a large number of I/O connections can be smoothly handled simultaneously and this is a big selling point of NodeJs.
A lot of callbacks can get messy quickly and that's why there also exists concepts like Promises and async functions, which are topics for another day.
I am running a very basic javascript code in Viusal Studio Code. However I am not seeing the callback function get triggered.
I am trying to understand how setTimeout() works in javascript. I am running the following code (in file called test.js) in Visual Studio code as follows:
node test.js
'use strict'
let timerexpired = false;
setTimeout( function() {console.log(`setting timerexpired to true...`);
timerexpired = true;
},
5000);
while(1) {
console.log(`timerexpired = `, timerexpired);
if (timerexpired) {
console.log(`timer expired. Resume execution`);
break;
} else {
console.log(`keep on spinning`);
}
}
Expect the loop to break after 5000 ms. But it keeps on spinning with output as "keep on spinning"
When you call setTimeout or when you do any blocking operation for that matter, it's in fact added to an event table and not executed immediately.
It's only after the call stack is empty that the event loop check the event table to see if there's anything waiting, and move it to the call stack.
To better understand this, check the following example:
setTimeout(() => console.log('first'), 0)
console.log('second')
You may think that first will be logged before second, but in fact it's second that will be logged first.
In your example the call stack is never empty, so setTimeout won't really work as expected.
You may wanna check this, it will give you a better idea about what's happening under the hood.
While(1) is an infinite while loop. I think that is the reason
I have encountered an unexpected behavior of JS setTimeout when modal dialog windows like alert are open and I would like to know the reason behind it.
I expected setTimeout(fn,10000) to mean "check current time periodically and when it is greater than Now + 10000ms fire the event handler that will invoke the passed 'fn' function". This would be logical, seeing how we pass the timeout measure as 'ms from now'. But, apparently, the countdown on setTimeout is a literal countdown and will be paused while a modal window is open.
setTimeout(function(){
//alert A
alert("10 seconds have passed for the first setTimeout")
}, 10000);
setTimeout(function(){
//alert B
alert("Wait for 15 seconds and press OK");
},1000);
I would expect alert A to display immediately after you close alert B (presuming you waited for 15 sec. to do so), since alert A timeout was just for 10 sec and they have already passed. Practice, however, shows that countdown to alert A is simply paused while alert B is open and it will show only after approx. 9 more seconds have passed after you've closed alert B, no matter how long B was open.
This does not seem logical.
Update. I'm definitely not the only one confused here, because this behavior of pausing the timeout occurs in Chrome and Internet Explorer, but not Firefox. Firefox executes the behavior I expected - if you wait for 15 seconds on alert B - alert A pops out instantly whenever you close it.
I doubt there is a definitive answer on why both IE and Chrome put a pause on pending timers until alert has been dismissed, and Firefox doesn't. I believe it's just because there is certain freedom in interpreting the W3C's specs for alert:
The alert(message) method, when invoked, must run the following steps:
If the event loop's termination nesting level is non-zero, optionally
abort these steps.
Release the storage mutex.
Show the given message to the user.
Optionally, pause while waiting for for the user to acknowledge the
message.
Step 4 (the pause) is further explained here:
Some of the algorithms in this specification, for historical reasons,
require the user agent to pause while running a task until a condition
goal is met. This means running the following steps:
If any asynchronously-running algorithms are awaiting a stable state,
then run their synchronous section and then resume running their
asynchronous algorithm. (See the event loop processing model
definition above for details.)
If necessary, update the rendering or user interface of any Document
or browsing context to reflect the current state.
Wait until the condition goal is met. While a user agent has a paused
task, the corresponding event loop must not run further tasks, and any
script in the currently running task must block. User agents should
remain responsive to user input while paused, however, albeit in a
reduced capacity since the event loop will not be doing anything.
So, the event loop gets suspended in any case. The callback of the longer timeout doesn't get invoked while the alert is still visible and modal. Had it not been this way, all kinds of nasties might be made possible, like multiple alerts on top of each other.
Now, can you tell from the above specs if the timer countdown should be suspended for the lifetime of the alert, or should rather be fired as soon as the alert has gone? I can't, and I'm not even sure which behavior would be more logical.
What I'm sure about is that you shouldn't be using JavaScript alerts for anything else but debugging purposes. Alerts do allow to suspend script execution (while some asynchronous operation like XHR is taking place in the background), but they're quite unfriendly to the user. The right approach would be embrace asynchronous code, using promises and, possibly, ES6 generators/yeild (if you're after the linear code style).
The following question is highly related and some alternatives to alert are discussed there:
Prevent js alert() from pausing timers
alert is UI blocking and since Javascript is single threaded it will block anything from running until the dialog is dismissed.
If you really need to, you can use your own timer.
var now = Date.now();
function alertA(){
//alert A
alert("10 seconds have passed for the first setTimeout")
}
setTimeout(function(){
//alert B
alert("Wait for 15 seconds and press OK");
setTimeout(alertA, 10000 - (Date.now() - now)); // Subtract the time passed
},1000);
You can wrap this in a utility method:
function alertDelay(messages, timePassed) {
if (typeof messages !== "object" || messages.length === 0) return;
if (timePassed == null) timePassed = 0;
var firstMessage = messages[0];
var now = Date.now();
setTimeout(function() {
alert(firstMessage.message);
alertDelay(messages.slice(1), Date.now() - now);
}, firstMessage.delay - timePassed);
}
Usage:
alertDelay([
{message: 'Message 1', delay: 1000},
{message: 'Message 2', delay: 5000},
{message: 'Message 3', delay: 10000}
]);
Given the single-threadedness of javascript, when a synchronous event is fired (for DOM manipulation for example) what happens to the currently executing function block when it is interrupted? How does the browser know where/when to continue execution of the interrupted block? Is there some sort of internal addition of a "pointer" to the event loop? I ask because I'm curious whether other waiting events in the event loop can intervene between the intervention of the synchronous event handler and the continuation of the original function block being executed.
And I am very early in my understanding of asynch events/synchronous events/event loop so if I have this totally wrong please let me know. But my understanding is that synchronous events (nested?) are "fired immediately" and I have seen it happen on jsfiddle with standard cut and paste from tutorials on the subject. I'm just confused as to how javascript knows how/where to pick up where it left off since it is so asynch driven.
A snippet:
<script>
var button = document.body.children[0]
var text = document.body.children[1]
button.onclick = function() {
alert('in onclick')
text.focus()
alert('out onclick')
}
text.onfocus = function() {
alert('onfocus')
text.onfocus = null //(*)
}
</script>
will produce 'in onclick', 'onfocus', 'out onclick'. The focus is a synchronous event. How does js know to pick back up with next statement after "text.focus()"? Is it something as simple as the something being done with the frame stack?
All the built-in events are asynchronous, but if you were to fire a synchronous event in your code, it would work like any regular function call that returns back once it's done. No event suddenly interrupts executing code - well pressing "stop execution" in developer tools actually does because the compiled code is littered with checks for that in every function and loop.