This code crashes chrome:
var ms = 1000;
function wait(ms) {
function _done() {
done = true;
alert('timer: ' + timer + ', done: ' + done);
clearTimeout(timer);
}
var done = false;
var timer;
alert('starting timer');
while (!done) {
timer = setTimeout(_done, ms);
}
alert('wait done');
}
Why? How do I get this right?
There are a lot of fundamental problems here.
First, I'm assuming that somewhere not depicted in your code, you're actually calling the wait function by passing it ms.
It looks like you want the variables done and timer to be accessible in your function _done. To ensure that, you should init your variables before you define the function.
setTimeout is async. Javascript is single threaded unless you go out of your way to start up other threads (web workers). setTimeout will not freeze processing for ms amount of time. It adds an event that will run if nothing else is executing. Because it is not blocking, your while loop will loop again to create another timeout, and another... ad infinium. None of the many timeouts will interrupt the while loop because of its single threaded nature. It's more like the Windows Message Queue. Since _done doesn't interrupt and set done to true, it'll continually spin off more and more timeouts until an inevitable crash.
If you're wanting to delay code execution by a certain amount of time, you setTimeout once and execution will begin after that wait as long as nothing else is executing.
function _done() {
alert('done!');
}
setTimeout(_done, 1000);
Another note, I don't think you have to clearTimeout for a timeout that has already timed out. Its only used to prevent a timeout from happening before it times out. This means that nine times out of ten, you can ignore the return value of setTimeout.
When you are setting the timer variable inside the loop, the loop does not wait for the timeout to execute before the loop continues.
So you are overwriting your timeout every time the loop occurs and the code will never be executed because your while loop does not take longer than a second to run the code.
See here for a similar problem : Wait until a condition is true?
You code will essentially change to something like the following :
var ms = 1000;
var done = false;
var timer;
function _done() {
done = true;
alert('timer: ' + timer + ', done: ' + done);
clearTimeout(timer);
wait(ms);
}
function wait(ms) {
if(!done) {
alert('starting timer');
timer = setTimeout(_done, ms);
} else {
//Is Done...
}
}
Once you call the wait function it will check to see if the done variable is true, if not it will call the _done function to set it to true. The _done function will then re-call the wait function to execute the condition again.
you are missing the following line
timer = setTimeout(_done(), ms);
JSFIDDLE
Related
I run a function in Javascript asynchronously by using the setinterval function.
myVar = setInterval(myTimer, 5000);
The execution of the myTimer function can be quite long, sometimes longer than the specified delay, in that case the intervals just get executed back to back. I would like the next execution of the callback function to be scheduled in relationship with the end of the execution of the previous. To restate I want the myTimer function to run after 5000 ms of when previous finishes and wanted this to repeat.
Yes this can be done using setTimeout instead.
function myTimer(){
console.log("Exec func");
// Rest of the functionality here
setTimeout(myTimer, 5000);
}
myTimer();
You can do something like that:
function longFunction() {
// Do your stuff here
}
var taskId;
function task() {
longFunction();
taskId = setTimeout(task, 5000);
}
I have a chess program that searches moves using a recursive alphaBeta algorithm (and some layers on top of that). I want to stop searching after 10 seconds (but I try with 1 second while making it work), so I use setTimeout to set a global flag.
let timeOut = false;
let i = 0;
setTimeout(() => {
timeOut = true;
console.log('timeout');
console.log('timeout');
console.log('timeout');
console.log('timeout');
}, 1000);
while (i < 1000 && timeOut === false) {
score = mtdf(pos, i, score)
console.log('depth:' + i + ', size: ' + tp.size());
i++;
}
This sure runs in the browser so I know the console.log statements should be printed in the browser but they aren't. The callback isn't called. Help please.
Edit: Added timeOut variable initialisation to make it more clear. Also, sorry, forgot to add i initialization (not that these declarations are even relevant to the question, since the problem is that setTimeout callback isn't even being executed...)
setTimeout will run the function you pass to it:
After the specified time has passed
After the minimum time for a timeout has passed
When the event loop isn't busy doing something else
… whichever is the latest.
You can't use a timeout to interrupt your while loop. The JavaScript engine is too busy running the while loop to check to see if there are any functions waiting to run on the timeout queue.
Your function will be called after the loop has finished when the JS engine is no longer busy.
Don't try to interrupt your loop from the outside.
Before you enter the while loop, record the current time.
Each time you go around the loop, compare the current time with the recorded time.
If the difference is greater than the period you want to allow the loop to run from, exit the loop
Javascript is single threaded meaning that execution of "asynchronous" statements like setTimeout, setInterval, and XMLHttpRequest get queued on a single thread. If something blocks the thread (like your heavy while() loop) async statements get delayed.
See here for more elaborate answer
To make your code work, I suggest looking at the timestamp
var t0 = new Date().getTime(), t1 = t0;
while(true)
{
// do stuff here
t1 = new Date().getTime();
if (t1 % 100 === 0) console.log(t1); // [debug ]print something every 100ms
if (t1 - t0 > 1000) break; // kill the loop after one second
}
setTimeout is asynchronous meaning the program won't wait for the setTimeout to finish and the code will continue executing.
You need to use Promise to wait for the setTimout to finish so your code will behave properly.
Also, the problem of your code also lies in the variable declarations, you didn't declare the i and timeOut variables.
let timeOut = false;
new Promise((resolve) => {
setTimeout(() => {
timeOut = true;
console.log('timeout');
console.log('timeout');
console.log('timeout');
console.log('timeout');
resolve();
}, 1000);
}).then(() => {
let i = 0;
while (i < 1000 && timeOut === false) {
score = mtdf(pos, i, score)
console.log('depth:' + i + ', size: ' + tp.size());
i++;
}
});
I have a function called using setInterval of JavaScript, which in some scenarios is called multiple times without the interval gap defined (I suspect this is because the intervals are not cleared properly and I'm creating multiple intervals, but I'm not sure).
I can not reproduce the problem locally.
The code uses Twirl but it's basically JS:
function refreshCheckInRequests() {
if (interval) { // If there is an interval running stop it.
clearInterval(interval);
}
jsRoutes.controllers.ExtranetSecuredController.findPendingCheckInRequests("#gymId").ajax({ // Ajax call using Play Framework
success: function (data) {
$("#checkin-request-container").html(data);
addRowListeners()
},
error: function (data) {
if (data.status == 401) {
errorSwitchGym("#Messages("extranet.switch.gym")");
//location.reload();
}
else {
unexpectedError(data)
}
},
complete: function() {
interval = initInterval(); // At the end of the call init the interval again
}
});
}
function initInterval() {
return setInterval(function () { refreshCheckInRequests(); },
20000);
}
var interval;
refreshCheckInRequests();
$("#checkin-request-refresh").click(function (event) {
refreshCheckInRequests();
event.preventDefault();
});
I could use setTimeout instead because at the end, I always call refreshCheckInRequests once, I stop the interval, and at the end I create a new one.
If I use timeout I have to call again my function at the end of the execution of the callback of timeout (like I'm doing right now). If something goes wrong, my callback will never be called again.
Anyway, I would like to know what's going on here. Am I missing something? Am I doing something wrong? Any suggestions?
You're clearing the current interval every time refreshCheckInRequests is called, but there is a delay between when refreshCheckInRequests is called and the new interval is assigned. Because refreshCheckInRequests also runs when an element is clicked, the following scenario could result in an unterminated interval:
User clicks, current interval is cleared, asynchronous findPendingCheckInRequests runs
User clicks again, no interval currently exists (nothing to clear), another asynchronous findPendingCheckInRequests runs
Response from first findPendingCheckInRequests comes back. complete handler runs, interval is assigned to the new interval
Response from second findPendingCheckInRequests comes back. complete handler runs, interval is assigned to the new interval over the old interval
The first created interval remains running, but there no longer exists a reference to it, so that first interval continues repeating forever.
So, try clearing the interval at the moment you reassign interval, ensuring that every new interval will always clear the old one, if an old one is running:
complete: function() {
clearInterval(interval);
interval = initInterval();
}
I have an ongoing while loop. In the while loop, there is a setTimeout(). I want to reset the timer AFTER the timer expires. In other words, when timer expires, it should do some specified actions, and then freshly start the timer again.
For example, in the following code, %%--Hi--%% should be printed only ONCE in 5 seconds. However, after 5 seconds, %%--Hi--%% is printed continuously. I tried clearTimeout but it looks like that clearTimeout can stop timer only before timer expires.
while(){
var timeoutHandle = setTimeout(function() {
console.log("%%--Hi--%%");
clearTimeout(timeoutHandle);
},
5000); //5 sec
}
Help please! Thanks!
It's not the timeout that's tripping you up, it's the infinite while loop. You're effectively creating thousands of timeouts every second. Moving the timeout outside of the infinite loop should solve your problem.
function timedAction() {
// your timed actions to be invoked every 5 seconds
setTimeout(timedAction, 5000); // start the next timeout
}
setTimeout(timedAction, 5000); // start the first timeout
while() {
// your continuously executing actions
}
There is no need to clear a timeout after it expires.
Alternatively you could also use setInterval, simplifying your code as follows:
function timedAction() {
// your timed actions to be invoked every 5 seconds
}
setInterval(timedAction, 5000); // start the interval
while() {
// your continuously executing actions
}
try this code:
logFun();
function logFun() {
console.log("%%--Hi--%%");
setTimeout(logFun, 5000);
}
or, you can try setInterval:
setInterval(function () {
console.log("%%--Hi--%%");
}, 5000);
The setTimeout method does not act like a sleep statement. Your while loop will continue to iterate continuously regardless of what time interval you set in the setTimeout method.
You should remove the while and simply use setInterval(). (Mentioned in yibuyisheng's answer. +1)
var myInterval = setInterval(function () {
console.log("%%--Hi--%%");
}, 5000);
If you want to stop the interval, run the following:
clearInterval(myInterval);
Your problem is that setTimeout is an asynchronous function, so your code doesn't wait for it to finish before looping again. To get around this you have to create your new timer inside the previous timer.
Likewise you have to call any actions you want to execute from inside the timer and can't just call them from in the while loop, because they won't wait for the timer to finish.
function logFun() {
console.log("%%--Hi--%%");
// execute actions
setTimeout(logFun, 5000);
}
This is what yibuyisheng has done with his answer that just showed up. (Still posting because the answer was not explained.)
Alternatively, if you must create the setTimeout() from the while loop, you could have a boolean (true/false) lock it. This is not as clean as yibuyisheng's answer, but it also allows you to add as much logic as you'd like to control enabling and disabling the timeout.
Example:
window.isTimeoutLocked = false;
while(true) {
if (!window.isTimeoutLocked) {
var myFunction = function () {
console.log("%%--Hi--%%");
//The timeout has fired. Unlock it for the next one.
window.isTimeoutLocked = false;
}
window.setTimeout(myFunction, 5000);
//Timeout is queued. Lock this code until the timeout fires.
window.isTimeoutLocked = true;
}
}
Also, I doubt that an infinite while loop is best for your upstream code. There is probably a callback that you can hook into, like requestAnimationFrame or some relevant event.
I always run into this problem and seem to implement a nasty looking solution.
It seems like a common design pattern to fire an action immediately, but not let that action queue up if clicked rapidly / delay firing if previously called within a timeframe. In my real world example, I have an AJAX call being made, so if I don't prevent repetitive actions the browser queues requests.
How would you implement this differently? What other options are there?
function myFunction() {
console.log("fired");
}
var timeout = null;
$("#foo").click(function() {
// if not previously clicked within 1 second, fire immediately
if (!timeout) {
myFunction();
timeout = setTimeout(function() {
timeout = null;
}, 1000);
} else {
// clicked again within 1s
clearTimeout(timeout); // clear it - we can't have multiple timeouts
timeout = setTimeout(function() {
myFunction();
timeout = null;
}, 1000);
};
});
With your current code, if you repeatedly click "#foo" at an interval slightly less than one second, say every 800ms, on first click it will fire the function immediately (obviously), but then it will fire the function exactly once more one second after the last click. That is, if you click ten times at 800ms intervals the function will fire once immediately and a second time approximately 8 seconds (800ms * 9 + 1000ms) after the first click.
I think you're better off removing the else case altogether, so that on click it will fire the function if it has not been called within the last second, otherwise it will do nothing with no attempt to queue another call up for later. Not only does that seem to me like a more logical way to operate, it halves the size of your function...
On the other hand, since you mentioned Ajax, rather than disabling the function based on a timer you may like to disable the function until the last Ajax request returns, i.e., use a flag similar to your timerid and reset it within an Ajax complete callback (noting that Ajax complete callbacks get called after success or failure of the request).
In the case of an auto-complete or auto-search function, where you want to send an Ajax request as the user types, you might want to remove the if case from your existing code and keep the else case, because for auto-complete you likely want to wait until after the user stops typing before sending the request - for that purpose I'd probably go with a shorter delay though, say 400 or 500ms.
Regarding general structure of the code, if I wanted a function to be fired a maximum of once per second I'd likely put that control into the function itself rather than in a click handler:
var myFunction = function() {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid=null; }, 1000);
// actual work of the function to be done here
console.log("myFunction fired");
};
}();
$("#foo").click(function() {
myFunction();
});
The immediately invoked anonymous function that I've added makes it uglier, but it keeps the timerid variable out of the global scope. If you don't like that obviously you could simply declare timerid in the same scope as myFunction() as you currently do.
This answer is getting kind of long, but if you have a lot of different functions that all need some kind of repeat control in them you could implement a single function to handle that part of it:
function limitRepeats(fn, delay) {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid = null; }, delay);
fn();
};
}
// myFunction1 can only be called once every 1000ms
var myFunction1 = limitRepeats(function() {
console.log("fired myFunction1()");
}, 1000);
// myFunction2 can only be called once every 3000ms
var myFunction2 = limitRepeats(function() {
console.log("fired myFunction2()");
}, 3000);
$("#foo").click(function() {
myFunction1();
myFunction2();
});