I have a button that is supposed to work in the following way:
When a user presses this button,setTimeout is called to wait a few seconds, and then execute a function.
Simplified, the code is as follows, were onButtonClick is called when the user presses the button:
function onButtonClick() {
// executes some trivial things
setTimeout(foo, 3000);
}
function foo() {
// do something
}
This works, however, the user is allowed (and even supposed to) click the button multiple times. Unintendedly, foo will, after waiting 3 seconds, be executed multiple times. Instead, I only wish foo to be executed 3 seconds after the last button click.
How can I achieve this behavior?
Use clearTimeout to cancel setTimeout.
That way whenever the user click the button again, the last timeout is canceled and the new timeout begins.
let timeout;
function onButtonClick() {
// executes some trivial things
clearTimeout(timeout);
timeout = setTimeout(foo, 3000);
}
function foo() {
console.log('foo');
}
<button onclick="onButtonClick()">click me</button>
Related
I have a vibrate function that I want to execute every 3 seconds or so until a button is clicked
My way of trying to do this is by having a loop that executes until a condition is false, when the button is clicked, the condition gets set to false. I could use sleep inside my while loop, followed by a call to vibrate, except that I would like to break as soon as the button is clicked, and not have to wait 3 seconds or so.
I am trying to set a timeOut if the device is still vibrating, but I don't want to continually set timeouts, I only want one timeout set at a time, so that a timeout is only set if one is not set already
this.vibrate()
while(this.state.isVibrating){
if (timeout has not been set){
setTimeout(this.vibrate(), 3000)
}
}
clearTimeout()
It sounds like setInterval is a better option here.
let interval;
this.vibrate();
interval = setInterval(this.vibrate.bind(this), 3000);
Then you can have a function execute when the button is clicked that can clear the interval:
function buttonClicked() {
clearInterval(interval);
}
Just make sure the interval var is in the scope of the buttonClicked function.
How can I stop/terminate a function which is already executed and still running? For example, I have this function:
function foo() {
setInterval(function() {
console.log("Foo Executed !");
}, 3000);
}
foo();
Now, this foo() function will run for unlimited time, when a specific event occurs, let's say a stop button has been clicked, then I want to stop this function.
In my case the function doesn't have setInterval() function. And what if the foo() function don't have the setInterval() method, but just many lines of code that get executed and I want to stop it from executing after a specific event.
Stopping a running function is a bit different than what you are actually showing for code, which is an asynchronous operation happening outside of the function that initiated it.
Running functions can only be terminated from within the function and that is done with either a return statement or by throwing an exception.
return can be called conditionally so that the function doesn't always exit at the same point. This is often the case with form validation functions - - if something is determined to be invalid, a return is encountered so that the form is not submitted. If everything is valid, the return is skipped and the form is submitted.
Here's a simple example with return:
function foo1(){
console.log("Foo started...");
if(prompt("Type 1 to terminate right now or anything else to continue...") == "1"){
return; // Function will terminate here if this is encountered
}
console.log("Foo ending..."); // This will only be run if something other than 1 was entered
}
foo1();
And, here's an example with throwing an error (not something that is usually done):
function foo(){
console.log("foo started...");
for(var i = 0; i < 5; i++){
if(i === 3) { throw "I HATE 3!"; }
console.log(i);
}
console.log("foo ended...");
}
foo();
But, with Timers and Intervals, you'll need to call clearInterval() and/or clearTimeout() to stop them. These are different because, while some function may initiate the timer or interval, the timer runs outside of the JavaScript runtime environment as a WebAPI. For these, we have to send a message to the WebAPI that we want the timer to stop counting.
You say:
Now, this foo() function will run for unlimited time, when a specific
event occurs, let's say a stop button has been clicked, then I want to
stop this function.
But foo isn't running for an unlimited time. It's run once and then terminates. Then approximately 3 seconds later, the timer calls for the anonymous function you passed to it to be run and then that function terminates and approximately 3 seconds later the anonymous function runs again and then it again terminates and so on. The function isn't running consistently, the interval timer (the WebAPI that calls for the function to be invoked) is.
And what if the foo() function don't have the setInterval() method,
but just many lines of code that get executed and I want to stop it
from executing after a specific event.
Your question seems to imply that you want to stop a function that is currently executing when another event takes place. This can't really happen in JavaScript since JavaScript is a single-threaded environment. Any event can only be raised and handled after all other code is done processing. So, there really can't ever be a scenario like the one you mention, unless we are talking about asynchronous code. Asynchronous code is code that runs outside of the JavaScript runtime. With that kind of code, you can send a message to the WebAPI that is processing that external code that you would like to cancel/abort that processing and that is what we're doing when we call clearInterval().
See below:
document.getElementById("start").addEventListener("click", startInterval);
document.getElementById("stop").addEventListener("click", stopInterval);
// You'll need a variable to store a reference to the timer
var timer = null;
function startInterval() {
// Then you initilize the variable
timer = setInterval(function() {
console.log("Foo Executed!");
}, 1500);
}
function stopInterval() {
// To cancel an interval, pass the timer to clearInterval()
clearInterval(timer);
}
<button type="button" id="start">Start</button>
<button type="button" id="stop">Stop</button>
For that use return; in the place you want to kill the process
I'm a newbie in this department, so I was wondering, can I make a type of if statement with a time delay? Example: if a certain action (maybe a click event) is done within a time period, the time is reset, and if it is not, a function is called.
You can't do this with an if statement, but you can with setTimeout and clearTimeout.
Here's an example of how you can make a function (a console.log statement) run every 2 seconds as long as you don't click the button. Clicking the button resets the timer so that another 2 seconds will need to pass before it begins logging again. You can adapt this to fit whatever actual work you need to happen.
var currentTimeoutId;
function resetTimeout() {
clearTimeout(currentTimeoutId);
currentTimeoutId = setTimeout(function() {
console.log('Too Late!');
resetTimeout();
}, 2000);
}
resetTimeout();
document.getElementById('demo').onclick = function() {
resetTimeout();
};
<button id="demo">Click Me Or Else I'll Log Something!</button>
I'm using setTimeOut to control an automatic slideshow.
(You can see it here: http://thingist.com/labs/ipad.shtml -- basically something pretty to look at while I'm working. Images are coming from reddit's API)
The code looks approximately like this:
next() {
image_url = images[key]["url"]
$("#image").html(vsprintf("<img src='%s'>", [image_url]));
key++;
setTimeOut(function() { next(); }, 30000);
The problem is that if I trigger the "next" function in another way (for instance with a div onclick), the setTimeOut callback function is still queued. So I'll "next" an image, but when the callback fires, it "next"s an image again. If you "next" many times in a row, there is an approx 30 second delayed burst that will follow you. (Once all of the queued timeouts fire).
Is there a way to prematurely trigger a setTimeOut's callback? Or to just dequeue it altogether?
You can use clearTimeout() to clear a previously set timeout.
var timeout;
function next() {
image_url = images[key]["url"]
$("#image").html(vsprintf("<img src='%s'>", [image_url]));
key++;
timeout = setTimeout(function() { next(); }, 30000);
}
// Clear the timeout by calling clearTimeout()
window.clearTimeout(timeout);
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();
});