Why do JS modal message boxes pause countdown on setTimeout()? - javascript

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}
]);

Related

Click during a while loop is not placed in event loop

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();

Why javascript event handler is performing differently in different browser?

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

Why setTimeout calls make different result

I have next code
setTimeout(function() {
setTimeout(function() {
console.log('foo');
}, 50);
}, 100);
setTimeout(function() {
setTimeout(function() {
console.log('baz');
}, 100);
}, 50);
The question was what's the output. But on running code I got different results on multiple runs - sometimes it's foo baz, sometime it's baz foo.
So there is two questions:
1) why I have different results?
2) Why sometimes I got baz foo ?
P.S. There is code snippet, but with code snippet I always get the same result
P.P.S. If it's environment specific - I use Chrome ( and FF ), but questions still actual
P.P.S. Possible answer was about usage console.log, but for
var a = [];
setTimeout(function() {
setTimeout(function() {
a.push('foo');
}, 50);
}, 100);
setTimeout(function() {
setTimeout(function() {
a.push('baz');
}, 100);
}, 50);
setTimeout(function() { console.log(a); }, 300);
it's still actual
The timeout specified is a minimum time that the browser should wait before executing the function, not a guaranteed time. If the browser is busy doing other things when the timer goes off, the function will be delayed.
So when you schedule the timer for 50 ms, it might not actually run until 53 ms later. Then it will set another timer for 100 ms after that, which is 153 ms after you started. Meanwhile, the timer that's set for 100 ms could run in 101 ms, and then set its second timer for 50 ms later, which is 151 ms after everything started. In this example, it will print foo bar.
Or you could get different delays, and the result would be bar foo.
If you need to perform actions in a specific sequence, you should run them sequentially in a single function, or call the second one from a callback of the first, or use promises in a specific order. Depending on precise millisecond timing with setTimeout is not reliable.
setTimeout doesn't schedule things after the EXACT time you specify - its a an approximation that is used by the javascript engine to schedule your functions as close as possible to the time you specify. In general, you shouldn't rely on timeouts to guarantee the order of execution. Assume that your timeouts can fall within a range of time, and don't expect the specified time to be the exact time that your functions will run.
Read here for more info.
Welcome to the world of asynchronous/event driven programming!
One of key things to understand about timers in javascript (and general purpose timing functions in nearly all languages) is that they are not designed to be precise to the tick. What occurs instead is that the program tells the operating system/js engine to "hey, send me a notification when at least this much time has elapsed." The operating system/js engine, however, may have hundreds, thousands, or even millions of tasks it needs to prioritize and execute, so it can't spend all of it's time just watching the clock waiting to fire off that notification. So, to save processing power, keeps all of these timing events in a queue, and only periodically checks to see how much time has gone by and if the events have expired.
In your particular case, you have an timeout event being created as a result of a timeout event being fired. So if the initial event is delayed slightly, that pushes back start time and thus the expiration of the second event. In your foo/baz example, if the initial foo timeout is delayed but the initial baz is not, then the baz callback will be added to the event queue before the foo callback is, and you get "baz foo".
Or sometimes baz will get delayed and foo won't, or sometimes neither will, or sometimes they both will. There's just too much going on under the hood (maybe not even related to your script/code/program) to be able to predict or rely on the exact execution order. That's the takeaway, and it's a good policy to live by for basically all of event driven programming.

Is using a while loop a good waiting strategy in a Firefox Restartless Extension?

I have a bootstrapped extension which interacts with the chrome part of Firefox (i.e. even before the content loads), and needs to query an SQLite database for some check. I would prefer a sync call. But, since a sync call is bad in terms of performance and can cause possible UI issues, I need to make an async DB call.
My use case is such:
Make aysnc call to database
Once completed do further processing
Now, this can be easily handled by placing 'further processing' part in handleCompletion part of executeAsync function.
But, I want the 'further processing' to be done irrespective of this statement being executed i.e. This DB lookup may or may not happen. If it doesn't happen well and good, go ahead. If it does I need to wait.
So, I am using a flag based strategy; I set a flag handleCompletionCalled in handleError & handleCompletion callback to true.
In the further processing part, I do a
while(handleCompletionCalled) {
// do nothing
}
//further processing
Is this a good strategy or can I do something better ( I don't really want to use Observers, etc. for this since I have many such cases in my entire extension and my code will be filled with Observers)?
Using a while loop to wait is a seriously Bad Idea™. If you do, the result will be that you hang the UI, or, at a minimum, drive CPU usage through the roof by rapidly running though your loop a large number of times as fast as possible.1
The point about asynchronous programming is that you start an action and then another function, a callback, is executed once the activity is completed, or fails. This either allows you to start multiple actions, or to relinquish processing to some other part of the overall code. In general, this callback should handle all activity that is dependent on the completion of the asynchronous action. The callback function, itself, does not have to include the code to do the other processing. After it has done what needs to happen in response to the async action completing, it can just call another function like doOtherProcessing().
If you launch multiple asynchronous, actions you can then wait for the completion of all of them by having flags for each task and a single function that is called at the end of all the different callback functions like:
function continueAfterAllDone(){
if(task1Done && task2Done && task3Done && task4Done) {
//do more processing
}else{
//Not done with everything, yet.
return;
}
}
This could be extended to an arbitrary number of tasks by using an array, or task queue, which the function then checks to see if all of those are completed rather than a hard-coded set of tasks.
Waiting:
If you are going to have another processing path which executes, but then must wait for the completion of the asynchronous action(s), you should have the wait performed by setting up a timer, or interval. You then yield the processor for a specified period of time until you check again to see if the conditions you need to proceed have occurred.
In a bootstrapped add-on, you will probably need to use the nsITimer interface to implement a timeout or interval timer. This is needed because at the time you are running your initialization code it is possible that no <window> exists (i.e. there may be no possibility to have access to a window.setTimeout()).
If you are going to implement a wait for some other task, you could do it something like:
const Cc = Components.classes;
const Ci = Components.interfaces;
var asyncTaskIsDone = false;
var otherProcessingDone = false;
// Define the timer here in case we want to cancel it somewhere else.
var taskTimeoutTimer;
function doStuffSpecificToResultsOfAsyncAction(){
//Do the other things specific to the Async action callback.
asyncTaskIsDone = true;
//Can either call doStuffAfterOtherTaskCompletesOrInterval() here,
// or wait for the timer to fire.
doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}
function doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval(){
if(asyncTaskIsDone && otherProcessingDone){
if(typeof taskTimeoutTimer.cancel === "function") {
taskTimeoutTimer.cancel();
}
//The task is done
}else{
//Tasks not done.
if(taskTimeoutTimer){
//The timer expired. Choose to either continue without one of the tasks
// being done, or set the timer again.
}
//}else{ //Use else if you don't want to keep waiting.
taskTimeoutTimer = setTimer(doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval
,5000,false)
//}
}
}
function setTimer(callback,delay,isInterval){
//Set up the timeout (.TYPE_ONE_SHOT) or interval (.TYPE_REPEATING_SLACK).
let type = Ci.nsITimer.TYPE_ONE_SHOT
if(isInterval){
type = Ci.nsITimer.TYPE_REPEATING_SLACK
}
let timerCallback = {
notify: function notify() {
callback();
}
}
var timer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(timerCallback,delay,type);
return timer;
}
function main(){
//Launch whatever the asynchronous action is that you are doing.
//The callback for that action is doStuffSpecificToResultsOfAsyncAction().
//Do 'other processing' which can be done without results from async task here.
otherProcessingDone = true;
doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}
Initialization code at Firefox startup:
The above code is modified from what I use for delaying some startup actions which do not have to be done prior to the Firefox UI being displayed.
In one of my add-ons, I have a reasonable amount of processing which should be done, but which is not absolutely necessary for the Firefox UI to be shown to the user. [See "Performance best practices in extensions".] Thus, in order to not delay the UI, I use a timer and a callback which is executed 5 seconds after Firefox has started. This allows the Firefox UI to feel more responsive to the user. The code for that is:
const Cc = Components.classes;
const Ci = Components.interfaces;
// Define the timer here in case we want to cancel it somewhere else.
var startupLaterTimer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
function startupLater(){
//Tasks that should be done at startup, but which do not _NEED_ to be
// done prior to the Firefox UI being shown to the user.
}
function mainStartup(){
let timerCallback = {
notify: function notify() {
startupLater();
}
}
startupLaterTimer = startupLaterTimer.initWithCallback(timerCallback,5000
,Ci.nsITimer.TYPE_ONE_SHOT);
}
Note that what is done in startupLater() does not, necessarily, include everything that is needed prior to the ad-on being activated by the user for the first time. In my case, it is everything which must be done prior to the user pressing the add-on's UI button, or invoking it via the context menu. The timeout could/should be longer (e.g. 10s), but is 5s so I don't have to wait so long for testing while in development. Note that there are also one-time/startup tasks that can/should be done only after the user has pressed the add-on's UI button.
1. A general programming issue here: In some programming languages, if you never yield the processor from your main code, your callback may never be called. In such case, you will just lock-up in the while loop and never exit.

Chrome calls timeouts before their time in particular timings window focus loss

Here is some code that runs a function every second using setTimeout and then checks that the function wasn't called too early.
var date = Date.now();
var ttl=1000,
to = setTimeout(function loop () {
console.log("triggered!");
if (Date.now() - date < ttl)
console.error("Yes");
date = Date.now();
to = setTimeout(loop, ttl);
}, ttl);
The console.error call should never occur. But if you are on chrome and hit Alt+Tab fast enough and enough times it will sometimes appear. I know chrome does some weird stuff with timeouts <1s when a tab looses focus but AFAIK it timeouts are only prolonged, never shortened.
EDIT: While I was writing this post I left such a loop running. When I went back at it here is what I see:

Categories

Resources