I have the following code:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
<div id="logger"></div>
<script>
function log(txt) {
document.getElementById('logger').innerHTML += txt + '<br>';
}
var int = 10;
var a= setTimeout(function(){
a = null;
log("A fired!");
clearTimeout(b);
b = null;
}, int);
var b = setTimeout(function(){
b = null;
log("B fired!");
clearTimeout(a);
a = null;
}, int);
</script>
</body>
</html>
Both timeout callbacks should prevent another another one from firing. In Opera, FF and Chrome only first one (that prints ″A fired″) is executed. But when I run the same code in IE6 and IE8, both callbacks are executed. Is that some error in my scriupt or is that one of those bugs that these browsers are full of? Do clearTimeout()/clearInterval() guarantee that callback won't be called after their invocation?
I think what is happening is that:
JavaScript has an event queue.
IE processes the timeouts, and queues two events.
The first timeout event is processed, and clearTimeout is called for B. However the event for B is already queued, so it still gets fired.
the second timeout event is processed, and clearTimeout is called for A.
I suspect that in IE, the event gets queued and calling clearTimeout does not remove the event from the event queue.
It is also possible there is just funkyness in how IE pushes simultaneous timeouts onto the queue... Diagnosing the underlying cause could be acheived by using two different timeouts, using 100% CPU processing loops for x time, and by queuing/slotting in other events (maybe can inject events into queue using window.postMessage() and catch them with window.onMessage()).
I have modified your existing code to demonstrate the problem better. It queues the log items rather than doing them immediately, because calling display() can cause layout or rendering to occur, which can easily introduce other funky interference.
Edit: You can test this using http://jsbin.com/ucukez/2 - if the browser has the fault then you get "in A timeout fired" and "in B timeout fired".
Edit: This was fixed in IE9 - I couldn't reproduce in IE9/IE10/IE11.
The HTML is:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>setTimeout queued test</title>
<script>
function display(txt) {
document.getElementById('logger').innerHTML += txt + '<br>';
}
var log = {
items: [],
push: function(text) {
this.items.push(text);
},
display: function() {
var items = this.items;
this.items = [];
for (var i = 0; i < items.length; i++) {
display(items[i]);
}
}
};
function startTest() {
var ms = 10;
display("startTest()");
log.push('before A setTimeout');
var a = setTimeout(function(){
log.push('in A timeout fired');
display("A fired!");
log.push('in A clear timer B');
clearTimeout(b);
log.push('in A cleared timer B');
}, ms);
log.push('after A setTimeout');
log.push('before B setTimeout');
var b = setTimeout(function(){
log.push('in B timeout fired');
display("B fired!");
log.push('in B clear timer A');
clearTimeout(a);
log.push('in B cleared timer A');
}, ms);
log.push('after B setTimeout');
setTimeout(function(){
display("");
display("Displaying logged items:");
log.display();
},1000);
}
</script>
</head>
<body onload="startTest()">
<div id="logger"></div>
</body>
</html>
You're setting the timeouts to occur at exactly the same time, and since they are both forked processes you get inconsistent results
Your best bet is to first test to see if the timeout is still valid like this:
var int = 10;
var a= setTimeout(function(){
if (!a) return;
a = null;
log("A fired!");
clearTimeout(b);
b = null;
}, int);
var b = setTimeout(function(){
if (!b) return;
b = null;
log("B fired!");
clearTimeout(a);
a = null;
}, int);
Related
I have a web page where a javascript calculation in a function takes lot of time to finish and makes the page to freeze. What technique should I use to make sure the javascript does not freeze the browser when the calculation is happening in the background?
If you only need to do a calculation and don't need to access the DOM during the long running calculation, then you have two options:
You can break the calculation up into pieces and do a piece at a time on a setTimeout(). On each setTimeout() call, the browser will be free to serve other events and will keep the page alive and responive. When you finish the last piece of the calculation, you can then carry out the result.
You can run the calculation in the background using a webworker in modern browsers. When the calcuation is done in the webworker, it sends a message back to the main thread and you can then update the DOM with the result.
Here's a related answer that also shows an example: Best way to iterate over an array without blocking the UI
Let me elaborate on #jfriend00's answer by giving a concrete stripped down example. Here is a long-running JavaScript process that can be started by clicking a button. Once it runs, it freezes the browser. The process consists of a long loop that repeats some workload where one iteration takes comparatively little time.
Due to the browser freeze, debugging a script like this is not easy. One alternative to avoid browser freeze is using a web worker. The drawback of that approach is the poor debuggabilty of web workers per se: Tools like Firebug are not supported.
<html>
<head>
<script>
var Process = function(start) {
this.start = start;
}
Process.prototype.run = function(stop) {
// Long-running loop
for (var i = this.start; i < stop; i++) {
// Inside the loop there is some workload which
// is the code that is to be debugged
console.log(i);
}
}
var p = new Process(100);
window.onload = function() {
document.getElementById("start").onclick = function() {
p.run(1000000000);
}
}
</script>
</head>
<body>
<input id="start" type="button" value="Start" />
</body>
</html>
Using a Queue data structure (e.g. http://code.stephenmorley.org/javascript/queues/), an interval timer and some small modification to the control flow of the original process one can build a GUI that doesn't freeze the browser, leaves the process fully debuggable and even allows additional features
like stepping, pausing and stopping.
Here is how it goes:
<html>
<head>
<script src="http://code.stephenmorley.org/javascript/queues/Queue.js"></script>
<script>
// The GUI controlling process execution
var Gui = function(start) {
this.timer = null; // timer to check for inputs and/or commands for the process
this.carryOn = false; // used to start/pause/stop process execution
this.cmdQueue = new Queue(); // data structure that holds the commands
this.p = null; // process instance
this.start = start;
this.i = start; // input to the modified process
}
Gui.prototype = {
/**
* Receives a command and initiates the corresponding action
*/
executeCmd: function(cmd) {
switch (cmd.action) {
case "initialize":
this.p = new Process(this);
break;
case "process":
this.p.run(cmd.i);
break;
}
},
/*
* Places next command into the command queue
*/
nextInput: function() {
this.cmdQueue.enqueue({
action: "process",
i: this.i++
});
}
}
// The modified loop-like process
var Process = function(gui) {
this.gui = gui;
}
Process.prototype.run = function(i) {
// The workload from the original process above
console.log(i);
// The loop itself is controlled by the GUI
if (this.gui.carryOn) {
this.gui.nextInput();
}
}
// Event handlers for GUI interaction
window.onload = function() {
var gui = new Gui(100);
document.getElementById("init").onclick = function() {
gui.cmdQueue.enqueue({ // first command will instantiate the process
action: "initialize"
});
// Periodically check the command queue for commands
gui.timer = setInterval(function() {
if (gui.cmdQueue.peek() !== undefined) {
gui.executeCmd(gui.cmdQueue.dequeue());
}
}, 4);
}
document.getElementById("step").onclick = function() {
gui.carryOn = false; // execute just one step
gui.nextInput();
}
document.getElementById("run").onclick = function() {
gui.carryOn = true; // (restart) and execute until further notice
gui.nextInput();
}
document.getElementById("pause").onclick = function() {
gui.carryOn = false; // pause execution
}
document.getElementById("stop").onclick = function() {
gui.carryOn = false; // stop execution and clean up
gui.i = gui.start;
clearInterval(gui.timer)
while (gui.cmdQueue.peek()) {
gui.cmdQueue.dequeue();
}
}
}
</script>
</head>
<body>
<input id="init" type="button" value="Init" />
<input id="step" type="button" value="Step" />
<input id="run" type="button" value="Run" />
<input id="pause" type="button" value="Pause" />
<input id="stop" type="button" value="Stop" />
</body>
</html>
While this approach certainly doesn't fit all long-running scripts one can think of, it certainly
can be adapted to any loop-like scenario. I'm using it to port Numenta's HTM/CLA artificial
intelligence algorithms to the browser.
Some browsers have only one thread for running your code and updating the UI (in other words, until the calculation is complete, the browser will appear "frozen"). You'll want to try to perform the action asynchronously, in one way or another.
If the calculation is really expensive, you might want to make a call to the server and let the server do the calculation, and callback the client when the calculation is done.
If the calculation is kind of expensive, you can try to do it in chunks on the client. This isn't actually asynchronous (as the client will block while executing each chunk) but the goal is to make the chunks small enough that the blocking is not noticeable.
setTimeout(function() { ..code }, 0);
I recommend this for heavy execution time, and also for on load ajax you could try to add
$(window).on("load", function (e) { }); // for jquery v3
if its in the loading process.
I think this should resolve your problem,
function myClickOperation(){
var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
setTimeout(function () { btn_savebutton2.click() }, 1000);
}
// Full Html content
<html>
<script>
function myClickOperation(){
var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
document.getElementById('savebutton1').disabled = true;
setTimeout(function () { btn_savebutton2.click() }, 1000);
}
function testClick(){
var idd = document.getElementById("myid");
idd.innerHTML =idd.innerHTML +"<br/>" + new Date();
if(true){
setTimeout(function () { testClick() }, 1);
}
}
</script>
<body>
<input type="button" id="savebutton1" onclick="myClickOperation()" value="Click me" />
<input type="button" id="savebutton2" onclick="testClick()" value="Do not click this" />
<input type="text"/>
<input type="button" value="temp"/>
<div style="height: 300px;overflow-y: scroll;" id="myid"/>
</body>
The following simple example is supposed to print a number in the console after clicking the button. If you click more than once in a short period of time, it will wait until all tasks are finished to print all the results (after calling awaitAll).
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
</head>
<body>
<button id="send">Send</button>
</body>
<script>
var button = document.querySelector('#send');
var q = queue(1);
var i = 0;
button.addEventListener('click', function() {
var message = i++;
q.defer(function(message, callback) {
setTimeout(function() {
callback(null, message);
}, 3000);
}, message);
});
q.awaitAll(function(err, result) {
console.log(result);
});
</script>
</html>
This works as intended. However, what happens if I want to reuse the queue again? For example, click on the button once and it will print [0]. Then click again and it will print [0,1]. The queue will keep track of previous tasks even after q.awaitAll has been called. I guess that could be useful but I have some concerns when the user provides tasks to the queue and after calling the callback, q.awaitAll outputs an array with all the previous results. The user probably needs the last one and returning everything seems a bit wasteful.
Is there some way to reset the queue?
By the way, I don't want to do this:
button.addEventListener('click', function() {
var q = queue(1);
var message = i++;
q.defer(function(message, callback) {
setTimeout(function() {
callback(null, message);
}, 3000);
}, message);
});
Here the queue doesn't work. In this case, I would be able to click several times and generate many queues running simultaneously.
Thanks
I have that Javascript counter:
var x=100;
function timerCountdown()
{
document.getElementById('timer1').value=x;
x--;
t=setTimeout("timerCountdown()",1000);
if (x<-1)
{
document.getElementById('timer1').value='Done!';
clearTimeout(t);
}
}
function stopCounter(){
clearTimeout(t);
x=x+1;
}
Then I use:
<body onFocus='timerCountdown()' onBlur='stopCounter()'>
But the problem is, the countdown doesn't start when the page loads. It waits for me to click on another window and to reFocus on the window again.
So I tried this:
<body onLoad='timerCountdown()' onFocus='timerCountdown()' onBlur='stopCounter()'>
But this time, the countdown goes pretty fast. Probably because timerCOuntdown is called twice every second.
Alternatively, I could just use the onFocus and onBlur in the body tag, but I need a function to trigger the Focus upon body load. Is that possible?
Does anyone have a suggestion to solve this problem?
thanks a lot!
The simple answer is because setTimeout is invoked twice, running timerCountdown() once for two times separately, and continually setting two setTimeout IDs.
This would be what you want:
var x = 100;
var t = 0;
function timerCountdown()
{
if (t == 0) t = setInterval(timerCountdown, 1000);
document.getElementById('timer1').value=x;
x--;
if (x < 0)
{
document.getElementById('timer1').value='Done!';
clearTimeout(t);
ticker = 0;
}
}
function stopCounter()
{
clearTimeout(t);
t = 0;
x++;
}
setInterval is much more suited for countdown timers, and things you need to run continually since setTimeout only runs once and you need to keep on calling it.
Edit: This fixes the initial rapid triggering of the timer on Firefox.
Remove the handler from <body onload= and add this to the end of the script block above:
t = setInterval(timerCountdown, 1000);
Try this in chrome versus firefox/IE:
var cancelPressed = false;
function redirect() {
//window.location = "http://www.google.com";
alert('hi!');
}
window.onbeforeunload = function() {
window.pressedTimer = setInterval("cancelPressed = true; clearInterval(window.pressedTimer);",3000);
window.onbeforeunload = function() {
if (!cancelPressed) {
window.unloadTimer = setTimeout('redirect()',500);
window.onbeforeunload = function() {clearTimeout(window.unloadTimer);};
return "Redirecting..";
} else {
return 'wups';
}
};
return 'first!';
};
In FF/IE, refresh, hit cancel on the first prompt, wait about six seconds, and then try to refresh. The 'wups' will be triggered. However, in Chrome, you can wait as long as you want and cancelPressed will never be set to true.
What do you think?
Which version of Chrome are you using? If I wait long enough, I also get the 'wups' message in Chrome. However, I noticed a subtle difference between Webkit browsers and other browsers. Consider the following code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Javascript test</title>
<script type="text/javascript" language="javascript">
window.onload = function() {
document.getElementById("test").onclick = function() {
var startdate;
var interval = window.setInterval(function() {
// Get the difference (in milliseconds) between the current
// date and the startdate.
var diff = (new Date()).getTime() - startdate.getTime();
alert(diff);
window.clearInterval(interval);
}, 5000);
alert("Hello!");
startdate = new Date();
};
};
</script>
</head>
<body>
<button id="test">Test button</button>
</body>
</html>
In chrome and safari, the second alert will always display a number slightly greater than 5000, while in other browsers, you get a number between 0 and 5000.
So what is happening? With setInterval(), the browser creates a timer that will invoke a javascript method every given interval. Javascript is single threaded, and an alert box will completely block the javascript execution. In chrome and safari, this means that the timer is also paused, while in other browsers the timer continues, but the javascript method invocation is suppressed until the alert box is closed.
What has this to do with your example? It means that in chrome and webkit you always have to wait at least 3000 milliseconds before cancelPressed is set, while in others browser, this will happen somewhere between 0 and 3000 milliseconds.
Ok, firstly, I hardly know Javascript. I really don't know what I'm doing.
So, I have this code:
var interval_id = 0;
var prevent_bust = 0;
// Event handler to catch execution of the busting script.
window.onbeforeunload = function() { prevent_bust++ };
// Continuously monitor whether busting script has fired.
interval_id = setInterval(function() {
if (prevent_bust > 0) { // Yes: it has fired.
prevent_bust -= 2; // Avoid further action.
// Get a 'No Content' status which keeps us on the same page.
window.top.location = 'http://vadremix.com/204.php';
}
}, 1);
function clear ()
{
clearInterval(interval_id);
}
window.onload="setTimeout(clear (), 1000)";
After 1 second I want to clear the interval set earlier. This isn't working. How would I do what I'm trying to do?
If you substitute the last line with window.onload = function() { setTimeout(clear, 1000); }, it should do OK.
There are two errors in your code:
window.onload should be a function, rather than a string ("..."),
setTimeout accepts a function (clear), rather than the result from the function (clear())
By the way, these are some good places to learn JavaScript:
QuirksMode
Mozilla Developer Network