Watching setTimeout loops so that only one is running at a time - javascript

I'm creating a content rotator in jQuery. 5 items total. Item 1 fades in, pauses 10 seconds, fades out, then item 2 fades in. Repeat.
Simple enough. Using setTimeout I can call a set of functions that create a loop and will repeat the process indefinitely.
I now want to add the ability to interrupt this rotator at any time by clicking on a navigation element to jump directly to one of the content items.
I originally started going down the path of pinging a variable constantly (say every half second) that would check to see if a navigation element was clicked and, if so, abandon the loop, then restart the loop based on the item that was clicked.
The challenge I ran into was how to actually ping a variable via a timer. The solution is to dive into JavaScript closures...which are a little over my head but definitely something I need to delve into more.
However, in the process of that, I came up with an alternative option that actually seems to be better performance-wise (theoretically, at least). I have a sample running here:
http://jsbin.com/uxupi/14
(It's using console.log so have fireBug running)
Sample script:
$(document).ready(function(){
var loopCount = 0;
$('p#hello').click(function(){
loopCount++;
doThatThing(loopCount);
})
function doThatOtherThing(currentLoopCount) {
console.log('doThatOtherThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatThing(currentLoopCount)},5000)
}
}
function doThatThing(currentLoopCount) {
console.log('doThatThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatOtherThing(currentLoopCount)},5000);
}
}
})
The logic being that every click of the trigger element will kick off the loop passing into itself a variable equal to the current value of the global variable. That variable gets passed back and forth between the functions in the loop.
Each click of the trigger also increments the global variable so that subsequent calls of the loop have a unique local variable.
Then, within the loop, before the next step of each loop is called, it checks to see if the variable it has still matches the global variable. If not, it knows that a new loop has already been activated so it just ends the existing loop.
Thoughts on this? Valid solution? Better options? Caveats? Dangers?
UPDATE:
I'm using John's suggestion below via the clearTimeout option.
However, I can't quite get it to work. The logic is as such:
var slideNumber = 0;
var timeout = null;
function startLoop(slideNumber) {
//... code is here to do stuff here to set up the slide based on slideNumber...
slideFadeIn()
}
function continueCheck() {
if (timeout != null) {
// cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
return false;
} else {
return true;
}
};
function slideFadeIn() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
$mySlide.fadeIn(fade, function() {
timeout = setTimeout(slideFadeOut,display);
});
}
};
function slideFadeOut() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
slideNumber=slideNumber+1;
$mySlide.fadeOut(fade, function() {
//... code is here to check if I'm on the last slide and reset to #1...
timeout = setTimeout(function(){startLoop(slideNumber)},100);
});
}
};
startLoop(slideNumber);
The above kicks of the looping.
I then have navigation items that, when clicked, I want the above loop to stop, then restart with a new beginning slide:
$(myNav).click(function(){
clearTimeout(timeout);
timeout = null;
startLoop(thisItem);
})
If I comment out 'startLoop...' from the click event, it, indeed, stops the initial loop. However, if I leave that last line in, it doesn't actually stop the initial loop. Why? What happens is that both loops seem to run in parallel for a period.
So, when I click my navigation, clearTimeout is called, which clears it.

What you should do is save the handle returned by setTimeout and clear it with clearTimeout to interrupt the rotator.
var timeout = null;
function doThatThing() {
/* Do that thing. */
// Schedule next call.
timeout = setTimeout(doThatOtherThing, 5000);
}
function doThatOtherThing() {
/* Do that other thing. */
// Schedule next call.
timeout = setTimeout(doThatThing, 5000);
}
function interruptThings() {
if (timeout != null) {
// Never mind, cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
}
}
When a navigation element is clicked simply call interruptThings(). The nice part is that it will take effect immediately and you don't need to do any polling or anything else complicated.

Related

Javascript mouseover run function, then wait and run again

I am trying to get a function to fire on mouseover of an element. However, if the function is already running I want it to not fire.
I have this for the mouseover trigger
onmouseover="runFunc()">
and I have this for the js:
var timerStarted = false;
function waitToStart() {
timerStarted = false;
}
function runFunc() {
if (!timerStarted) {
console.log('run function');
}
timerStarted = true;
setTimeout(waitToStart, 6000);
}
When I first hover over the element, I get
run function
and nothing happens for 6 seconds which seemed like it was working. However after 6 seconds the console output counted fast up to the amount of times I hovered over the function:
(24) run function
I'm struggling to understand why and how to make sure that the function only fires every 6 seconds after mouseover.
You're starting many timers. And eventually they all end and each set your boolean to false, even when in the half second before that you still started yet another timer.
What you really want is to either cancel any previously launched timer before issuing a new one, or to only launch a new one when you are sure the preceding one already timed out. The effect is a bit different: the first solution will reset the countdown with every mousemove (so must hold it still for at least 6 seconds), while the second will just make sure that there are at least 6 seconds between executions of the if block.
Here is how the second approach works:
var timerStarted = false;
function waitToStart() {
timerStarted = false;
}
function runFunc() {
if (!timerStarted) {
console.log('run function');
timerStarted = true; // <--- moved inside the IF block
setTimeout(waitToStart, 6000); //
}
}

JavaScript setTimeout() duplicates

I'm fairly new to JavaScript/jQuery, but have made a script to change the background picture.
First Script
The first script version works fine and does the following:
creates a setInterval timer that calls function backgroundChange() to run every 7 seconds
decides the next picture URL
sets the background picture
This works great, but the problem is when the website is live, on a slow connection the background picture doesn't load in time for the next timer change.
New Script
So the new version:
creates a setTimeout timer that calls function backgroundChange() to run after 7 seconds
var theTimer = setTimeout(backgroundChange, 7000);
clearsTimeout (surely I shouldn't have to run this?)
window.clearTimeout(theTimer);
decides the next picture URL
waits until the picture is loaded:
then sets the background picture
then adds a new setTimeout timer
$('#testImage').attr('src', imageText).load(function()
{
$('#backgroundTop').fadeIn(timeIn,function()
{
theTimer = setTimeout(backgroundTimer, 7000);
});
});
The problem is that the timer now seems to be called double the amount of times whenever the timer runs and exists in the .load function.
I havent purposely not posted my code yet, as I want to make sure my understanding is correct first, rather than someone just fixing my code.
Ta very much.
Instead of unbinding, you could use a JavaScript closure for the timer function. This will maintain a single timer that is reset every time it is called.
var slideTimer = (function(){
var timer = 0;
// Because the inner function is bound to the slideTimer variable,
// it will remain in score and will allow the timer variable to be manipulated.
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
Then in your code:
$('#testImage').attr('src', imageText).load(function() {
$('#backgroundTop').fadeIn(timeIn,function()
{
slideTimer(backgroundTimer, 7000);
});
});
There should be no need to clear or set the timer anywhere else in your code.
You need to unbind the load handler before you add the next one, since they keep piling up as your code stands. With every iteration, you add an extra handler that does the exact same thing. Use unbind to remove the old handler before you reattach:
$('#testImage').unbind('load');

Javascript - Stop a repeating function [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to pause a setTimeout call ?
I have a function that gets called on page load which starts off a repeating function:
setTimeout(function () {
repeat();
}, 8000)
This function calls repeat() every 8 seconds, inside this function I have a bit of ajax which updates a counter on the page. Clicking on the counter gives the user a drop down menu with a number of messages. The counter value equals the number of messages the user has. Kind of like Facebook notifications.
When clicking the drop down menu Im using jQuery to hide and show it:
$('#messages').click(function () {
$('#messagesDropDown').slideDown();
})
.mouseleave(function () {
$('#messagesDropDown').slideUp();
});
When the #messagesDropDown is visible I want to stop the repeat() function, to prevent the list of messages from updating while Im viewing the current ones.
On .mouseleave I want to start the repeat() function again.
Anyone have any ideas how I can 'STOP' a repeating function In the .click function and start it again on .mouseleave ?
setTimeout returns a ID of the timeout. You can store that value, and then use clearTimeout to stop the timeout when you want.
var timeout;
$('#messages').click(function () {
$('#messagesDropDown').slideDown(function () {
clearTimeout(timeout); // Cancel the timeout when the slideDown has completed.
});
})
.mouseleave(function () {
$('#messagesDropDown').slideUp();
clearTimeout(timeout); // Cancel incase it's still running (you can also set `timeout` to undefined when you cancel with clearTimeout, and apply some logic here (`if (timeout == undefined)` so you can leave it running rather than restarting it)
timeout = setTimeout(repeat, 8000); // Store the ID of the timeout
});
setTimeout will not set a recurring event; it will only fire once (like a delayed event). Look at setInterval (and clearInterval) instead.
You said that this code starts a repeating function:
setTimeout(function () {
repeat();
}, 8000)
Since setTimeout doesn't repeat, I assume that the repeat function itself fires off another setTimeout to call itself again after it runs (chained setTimeout calls).
If so, you have two options:
Have a control variable telling repeat whether to do its work or not. A simple boolean will do. Set the boolean when you want repeat to skip its work, and have repeat check it. This is the dead simple answer.
Have control functions for repeat, like so:
var repeatHandle = 0;
function startRepeat() {
if (!repeatHandle) {
repeatHandle = setTimeout(repeatTick, 8000);
}
}
function repeatTick() {
repeatHandle = 0;
repeat();
}
function stopRepeat() {
if (repeatHandle) {
clearTimeout(repeatHandle);
repeatHandle = 0;
}
}
...and then use them to control the repeats. Be sure to modify repeat to call startRepeat to schedule its next call rather than calling setTimeout directly.

jQuery infinite function execution

We want to know if it is possible to have a function using jQuery to inspect a number of elements and, depending on the types assigned to them by one click, perform other functions. Basically, a function that would run forever, while the user does not refresh the page.
The idea is not to depend on events clicks to perform a function, but the classes assigned to a specific element.
For example:
$("td.gantt").each(function() {
if($(this).hasClass("oper")) {
//execute a serie of functions
}
if($(this).hasClass("preop")) {
//execute a serie of functions
}
});
The above is executed once, and we need to run all the time.
// define a function...
function ganttEach() {
$("td.gantt").each(function() {
// ...
});
}
// ...repeat it once every second
window.setInterval(ganttEach, 1000);
You can't "let it run all the time" (like, in a while(true) loop) because JavaScript is single-threaded and blocking the thread means your other code will never run. setInterval() makes sure there are necessary "gaps" for other code to execute.
setInterval() returns an ID that you can store in a variable and feed to clearInterval() at some point to make it stop again.
If you want to make sure that every new iteration of your function starts only after the previous one has really finished, use setTimeout() instead:
// define a self-repeating function...
function ganttEach() {
$("td.gantt").each(function() {
// ...
});
window.setTimeout(ganttEach, 1000); // calls itself again in one second
}
// ...initiate self-repeating function
ganttEach();
You should probably include some way to stop the endless repetition here as well, like introducing a flag that's checked before the setTimeout() call.
You can run your check every few milliseconds, say 50ms, using setInterval
window.setInterval (function () {
// do checks here
}, 50);
You might end up using a lot of CPU power if your checks are too frequent, or too complicated.
It is possible, with setInterval. My advice would be to select the element outside of the repeating function so as to minimize the overhead.
An infinite loop would lock the browser UI, as it is a single threaded environment. Set interval, however let you add actions to the UI stack which will be executed after a given period of time. You can specify this period in the second parameter of setInterval.
// select the element outside
// to minimize overhead
$gantt = $("td.gantt");
// define a repeating action
setInterval(function() {
$gantt.each(function() {
if($(this).hasClass("oper")) {
//execute a serie of functions
}
if($(this).hasClass("preop")) {
//execute a serie of functions
}
});
}, 100); // repeat interval: 100ms
I am not sure exactly what you are trying to do, but have you tried setInterval? It will keep running if that is what you really want.
window.setInterval(function () {
// add your jQuery here
}, 100);

How do I prevent the concurrent execution of a javascript function?

I am making a ticker similar to the "From the AP" one at The Huffington Post, using jQuery. The ticker rotates through a ul, either by user command (clicking an arrow) or by an auto-scroll.
Each list-item is display:none by default. It is revealed by the addition of a "showHeadline" class which is display:list-item. HTML for the UL Looks like this:
<ul class="news" id="news">
<li class="tickerTitle showHeadline">Test Entry</li>
<li class="tickerTitle">Test Entry2</li>
<li class="tickerTitle">Test Entry3</li>
</ul>
When the user clicks the right arrow, or the auto-scroll setTimeout goes off, it runs a tickForward() function:
function tickForward(){
var $active = $('#news li.showHeadline');
var $next = $active.next();
if($next.length==0) $next = $('#news li:first');
$active.stop(true, true);
$active.fadeOut('slow', function() {$active.removeClass('showHeadline');});
setTimeout(function(){$next.fadeIn('slow', function(){$next.addClass('showHeadline');})}, 1000);
if(isPaused == true){
}
else{
startScroll()
}
};
This is heavily inspired by Jon Raasch's A Simple jQuery Slideshow.
Basically, find what's visible, what should be visible next, make the visible thing fade and remove the class that marks it as visible, then fade in the next thing and add the class that makes it visible.
Now, everything is hunky-dory if the auto-scroll is running, kicking off tickForward() once every three seconds. But if the user clicks the arrow button repeatedly, it creates two negative conditions:
Rather than advance quickly through the list for just the number of clicks made, it continues scrolling at a faster-than-normal rate indefinitely.
It can produce a situation where two (or more) list items are given the .showHeadline class, so there's overlap on the list.
I can see these happening (especially #2) because the tickForward() function can run concurrently with itself, producing different sets of $active and $next.
So I think my question is:
What would be the best way to prevent concurrent execution of the tickForward() method?
Some things I have tried or considered:
Setting a Flag: When tickForward() runs, it sets an isRunning flag to true, and sets it back to false right before it ends. The logic for the event handler is set to only call tickForward() if isRunning is false. I tried a simple implementation of this, and isRunning never appeared to be changed.
The jQuery queue(): I think it would be useful to queue up the tickForward() commands, so if you clicked it five times quickly, it would still run as commanded but wouldn't run concurrently. However, in my cursory reading on the subject, it appears that a queue has to be attached to the object its queue applies to, and since my tickForward() method affects multiple lis, I don't know where I'd attach it.
You can't have concurrent executions of a function in javascript. You just have several calls waiting to execute in order on the pile of execution. So setting a flag when the function runs cannot work. When the event handler runs, it cannot run concurrently with a tickHandler() execution (javascript is threadless).
Now you have to define precisely what you want, because it doesn't appear in your question. What you happen when the user clicks, say, 3 times in rapid succession on the arrow? And how do the clicks interfere with the auto-scroll?
I'd say the easiest way would be to process a click only when the ticker is idle, so 3 clicks in a row will only tick once. And you make clicks replace auto-scroll and reset its timer. So I use a flag ticksInQueue that is raised when a tick is queue by a click and only lowered when the fadeIn has completed:
var ticksInQueue = 0,
timerId = setInterval(tickForward, 5000),
isPaused = false;
function tickForward() {
var $active = $('#news li.showHeadline');
var $next = $active.next();
if($next.length==0) $next = $('#news li:first');
$active.stop(true, true);
$active.fadeOut('slow', function() {
$active.removeClass('showHeadline');
});
setTimeout(function(){
$next.fadeIn('slow', function(){
$next.addClass('showHeadline');
if(ticksInQueue) ticksInQueue--; // only change
})}, 1000);
if(isPaused == true){
} else {
startScroll()
}
}
$('#arrow').click(function () {
if(ticksInQueue) return;
ticksInQueue++;
clearInterval(timerId);
timerId = setInterval(tickForward, 5000);
tickForward();
});
You can try a demo here : http://jsfiddle.net/mhaCF/
You can set up a bool variable and check it's value when the function executes. If it's already being run you can either wait for it to end or just skip it (don't know which is desirable)
var active = false;
function tickForward() {
if (!active) {
active = true;
// your function code goes here
// remember to change active to false once you're done
active = false;
} else {
// if you want to skip if active = true you don't need this else
// if you want to call your method later use something like this
setTimeout(tickForward, 1000);
}
}
This implementation using "new Date()" and "setTimeout" to avoid some race condition (concurrent execution) for some resource. That way each call to "NoConcurrExecFunc" is executed with at least 750 milliseconds between each one!
var last_call = new Date();
function NoConcurrExecFunc(param_a, param_b) {
var new_date = new Date();
var min_interval = 750;
if ((last_call - new_date) < 0) {
last_call = new Date(new_date.getTime() + min_interval);
// Do your stuff!
} else {
setTimeout(function () {
NoConcurrExecFunc(param_a, param_b);
}, min_interval);
}
}

Categories