JavaScript setTimeout() duplicates - javascript

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

Related

Wrong use of Javascript setInterval()

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

How setInterval and setTimeout behave on Phonegap/Cordova when app resumes from background? [duplicate]

I have a jQuery Mobile web app which targets iOS and Android devices. A component of the application is a background task, which periodically checks for a.) changes to local data and b.) connectivity to the server. If both are true, the task pushes the changes.
I'm using a simple setTimeout()-based function to execute this task. Each failure or success condition calls setTimeout() on the background task, ensuring that it runs on 30 second intervals. I update a status div with the timestamp of the last task runtime for debugging purposes.
In any desktop browser, this works just fine; however, on iOS or Android, after some period of time, the task stops executing. I'm wondering if this is related to the power conservation settings of the devices--when iOS enters stand-by, does it terminate JavaScript execution? That is what appears to happen.
If so, what is the best way to resume? Is there an on-wake event which I can hook into? If not, what other options are there which don't involve hooking into events dependent on user interaction (I don't want to bind the entire page to a click event just to restart the background task).
Looks like Javascript execution is paused on MobileSafari when the browser page isn't focused. It also seems if setInterval() events are late, they are simply fired as soon as the browser is focused. This means we should be able to keep a setInterval() running, and assume the browser lost/regained focus if the setInterval function took much longer than usual.
This code alerts after switching back from a browser tab, after switching back from another app, and after resuming from sleep. If you set your threshold a bit longer than your setTimeout(), you can assume your timeout wouldn't finish if this fires.
If you wanted to stay on the safe side: you could save your timeout ID (returned by setTimeout) and set this to a shorter threshold than your timeout, then run clearTimeout() and setTimeout() again if this fires.
<script type="text/javascript">
var lastCheck = 0;
function sleepCheck() {
var now = new Date().getTime();
var diff = now - lastCheck;
if (diff > 3000) {
alert('took ' + diff + 'ms');
}
lastCheck = now;
}
window.onload = function() {
lastCheck = new Date().getTime();
setInterval(sleepCheck, 1000);
}
</script>
Edit: It appears this can sometimes trigger more than once in a row on resume, so you'd need to handle that somehow. (After letting my android browser sleep all night, it woke up to two alert()s. I bet Javascript got resumed at some arbitrary time before fully sleeping.)
I tested on Android 2.2 and the latest iOS - they both alert as soon as you resume from sleep.
When the user switches to another app or the screen sleeps, timers seem to pause until the user switches back to the app (or when the screen awakens).
Phonegap has a resume event you can listen to instead of polling for state (as well as a pause event if you want to do things before it is out of focus). You start listening to it after deviceReady fires.
document.addEventListener("deviceready", function () {
// do something when the app awakens
document.addEventListener('resume', function () {
// re-create a timer.
// ...
}, false);
}, false);
I use angular with phonegap and I have a service implemented that manages a certain timeout for me but basically you could create an object that sets the timer, cancels the timer and most importantly, updates the timer (update is what is called during the 'resume' event).
In angular I have a scopes and root scope that I can attach data to, my timeout is global so I attach it to root scope but for the purpose of this example, I'll simply attach it to the document object. I don't condone that because you need should apply it to some sort of scope or namespace.
var timeoutManager = function () {
return {
setTimer: function (expiresMsecs) {
document.timerData = {
timerId: setTimeout(function () {
timeoutCallback();
},
expiresMsecs),
totalDurationMsecs: expiresMsecs,
expirationDate: new Date(Date.now() += expiresMsecs)
};
},
updateTimer: function () {
if (document.timerData) {
//
// Calculate the msecs remaining so it can be used to set a new timer.
//
var timerMsecs = document.timerData.expirationDate - new Date();
//
// Kill the previous timer because a new one needs to be set or the callback
// needs to be fired.
//
this.cancelTimer();
if (timerMsecs > 0) {
this.setTimer(timerMsecs);
} else {
timeoutCallback();
}
}
},
cancelTimer: function () {
if (document.timerData && document.timerData.timerId) {
clearTimeout(document.timerData.timerId);
document.timerData = null;
}
}
};
};
You could have the manager function take a millisecond parameter instead of passing it into set, but again this is modeled somewhat after the angular service I wrote. The operations should be clear and concise enough to do something with them and add them to your own app.
var timeoutCallback = function () { console.log('timer fired!'); };
var manager = timeoutManager();
manager.setTimer(20000);
You will want to update the timer once you get the resume event in your event listener, like so:
// do something when the app awakens
document.addEventListener('resume', function () {
var manager = timeoutManager();
manager.updateTimer();
}, false);
The timeout manager also has cancelTimer() which can be used to kill the timer at any time.
You can use this class github.com/mustafah/background-timer based on #jlafay answer , where you can use as follow:
coffeescript
timer = new BackgroundTimer 10 * 1000, ->
# This callback will be called after 10 seconds
console.log 'finished'
timer.enableTicking 1000, (remaining) ->
# This callback will get called every second (1000 millisecond) till the timer ends
console.log remaining
timer.start()
javascript
timer = new BackgroundTimer(10 * 1000, function() {
// This callback will be called after 10 seconds
console.log("finished");
});
timer.enableTicking(1000, function(remaining) {
// This callback will get called every second (1000 millisecond) till the timer ends
console.log(remaining);
});
timer.start();
Hope it helps, Thank you ...
You should use the Page Visibility API (MDN) which is supported just about everywhere. It can detect if a page or tab has become visible again and you can then resume your timeouts or carry out some actions.

clearTimeout on a function that has a paramenter

I am writing a chat application that can have many chat windows open at once. Every time a window is opened I call a setInterval on my function, update_chat(), that updates an individual chat window. I pass the chat_id to update_chat()
setInterval("update_chat("+chat_id+")",4000);
chat_id is just the id of the chat. So I can have the function update_chat running multiple times on different intervals depending on how many chats are open. Start the chat works fine.
My main question is how can I stop the interval above. I don't want to stop all intervals, just the one associated with a particular chat. I tried this
clearInterval("update_chat("+chat_id+")");
but it didn't do anything.
I tried
var chat_intervals=[]
chat_intervals[chat_id]=setInterval("update_chat("+chat_id+")",4000);
clearInterval(chat_intervals[end_id]);
It didn't stop the interval
clearInterval(docs) takes an interval ID as a parameter to know which interval to clear. setInterval (docs) returns an interval ID when called, so you store that in a var and pass it to clearInterval when you want that one to clear.
//start the interval, store its ID
var interval_id = setInterval( function () { /* do something*/ }, 1000);
//clear the interval
clearInterval(interval_id);
Note that setTimeout (docs) and clearTimeout (docs) work with each other in the same way.
Also note that while setInterval can take a string argument as the function to run, it can also take actual functions (which is highly preferred). So your code could be better written as:
var update_chat_interval = setInterval(function () {
update_chat(chat_id);
}, 4000);
// to clear it later:
clearInterval(update_chat_interval);

Restarting a setInterval() in Javascript/jQuery (without clearInterval)

I'm working on ui tabs built using jQuery. Everything works except for one issue - I did a setInterval that runs a function that does a trigger("click") so that it goes to the next tab after 5000 miliseconds. It runs through each tab fine, the issue is that if the user manually clicks on a tab, the timer for the setInterval does not restart back at 0. For example if a user were to start on tab1 at 0 miliseconds and clicks on tab2 at 2000 miliseconds, the setInterval doesn't go back to 0, it would start at 2000 and run to 5000 miliseconds and would subsequently goto tab3. I understand why it's happening, I just wonder if there were a way to restart the setInterval timing without having to do a clearInterval() and creating an entirely new setInterval(). Any insight would be appreciated.
Update
Thanks for the replies - The reason I was trying to avoid using clearInterval was because I was having issues of how to write the code in a way where the clearInterval would stop the setInterval completely. The code is setup to track whenever a user has clicked a tab. The problem is the auto change function utilizes trigger('click'), so it runs the clearInterval function I wrote also when the tabs auto-change. It seems to run fairly fine on its own, but once the user starts clicking on tabs, the setInterval behaves unusually and switches tabs unpredictably. I suspect what is happening is that several setIntervals are running at once... Here's the code (If you haven't guessed it already, I'm pretty new at javascript/jquery). I've commented out parts so that it's functional, but it still doesn't function as I intended (from first post).
// auto change tabs
if( options.interval ) {
function timerCom() {
if( !$(".controller").hasClass('paused') ) {
var i = $(".tab-current > a").attr("rel");
//alert(i);
if( i == 3 ) {i = 0};
$container
.find('a')
.eq(i)
.trigger('click');
}
}
//$("#promo-items > li > a").click(function () {
//var timer;
//if( timer != null ) {clearInterval(timer);}
timer = setInterval(timerCom, options.interval);
//});
}
No, there is no way to restart a timer set by setInterval without clearing the timer.
You can't really alter intervals or timeouts, only clear them. That said it should be a simple thing to create a function that clears the interval, and then starts a new but identical one immediately with a fresh time value.
var intervalID;
var resetTimer = function() {
if (intervalID) { clearInterval(intervalID) };
intervalID = setInterval(function() {
console.log('doing stuff!');
}, 5000);
};
timer = setInterval(function() {
timerCom();
}, options.interval);
I know this post is well over 2 years old, but I ran into a similar problem just now, and I found a solution.
I was writing an image scroller that would automatically shift to the next image after a set amount of time, and whenever I clicked the navigation buttons, the transitions moved double-time.
Here's my solution:
Make the interval variable (timer in your case) somewhat global.
i.e. in the options section (assuming it was defined earlier, and then later assigned), add a null timer variable.
var options = {
'interval',
//Other variables
'timer',
};
Then, call clearInterval twice when you handle the click event.
$("#promo-items > li > a").click(function () {
if( options.timer != null ) {
clearInterval(options.timer);
clearInterval(options.timer);
}
options.timer = setInterval(timerCom, options.interval);
});
Worked like a charm for me.
Again, sorry if this is wayyyy too late.

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

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.

Categories