how to make sure two setInterval() will not affect each other? - javascript

I have 2 setInterval function (okay guys, sorry, i thought the codes inside may be redundant and will make the question become localized :/ but anyway, here it is:
$('#armStatus').click(function(){
armingloop = setInterval(function () {
if ($checkbox.is(':checked ')) {
$.post('/request', {
key_pressed: "arming_status"
}).done(function (reply) {
$arm.empty().append("<h3>The Arming Status is " + reply + "</h3>").show();
$arm.show();
});
} else {
$arm.hide();
}
}, 3000);
});
and
$('#monitor').click(function () {
bigloop = setInterval(function () {
var checked = $('#status_table tr [id^="monitor_"]:checked');
if (checked.index() === -1 || checked.length === 0) {
clearloop(bigloop);
$('#monitor').button('enable');
} else {
//$('#monitor').button('enable'); //enable the monitor button
(function loop(i) {
//monitor element at index i
monitoring($(checked[i]).parents('tr'));
//delay of 3 seconds
setTimeout(function () {
//when incremented i is less than the number of rows, call loop for next index
if (++i < checked.length) loop(i);
}, 3000);
}(0)); //start with 0
}
}, index * 3000); //loop period
});
function clearloop(loopname){
bigloop= window.clearInterval(loopname);
}
Both will be triggered by different selector. I observe that when the bigloop is activated, and armingloop is also activated at a later time, the status update function monitoring in my bigloop is affected (e.g. status reply is captured by wrong element.)
Note that i have a setTimer as well.
My question here is, how can i make sure any 2 setIntervals are isolated and will not affect each other?

You simply can't as they have no guarantee of order. They are added to an event queue together with other events (incl. repaints etc.), and which ever comes first is called.
A better implementation would be in your main loop to throw a CustomEvent which your monitor is listening to.
Simplified:
// global flag for monitoring
var isMonitoring = true,
armingloop;
// the function we use to update monitor.
// This will be called every time we send an event
function updateMonitor(e) {
/* ... update ... */
// ie. e.detail.mydata
}
// Start listening to 'monitor' event. If received, call
// the function above (only reference the function).
window.addEventListener('monitor', updateMonitor, false);
// The main loop. Self-triggering for loop by calling
// setTimeout.
// Do the stuff you need and then, if monitor is
// enabled create an event and dispatch (send) it.
function loop() {
/* ... main stuff ... */
// do we monitor?
if (isMonitoring) {
// something need to be updated on monitor so
// create an event
var myEvent = new CustomEvent('monitor', {
detail: {
/* here you can provide needed data for monitor */
"mydata": data /*, other data */
},
/* If you don't need to send any data in particular,
just leave detail empty like this:
detail: {},
*/
bubbles: false,
cancelable: true
});
// send event to anyone who listens..
window.dispatchEvent(myEvent);
}
//here you can use a use a flag to stop the loop,
//f.ex. if 'isLooping' === true then setTimeout...
armingloop = setTimeout(loop, 3000);
}
function toggleMonitor() {
// Call this from the toggle button, or modify to
// reflect checkbox-status etc...
isMonitoring = !isMonitoring;
}
//start everything:
loop();
I changed the example a bit from setInterval to setTimeout to avoid stacking/blocking. Also keep in mind that Javascript is single-threaded (with a few exceptions that are not relevant here). For this reason setTimeout is a better choice (call it from inside the loop).

How can I make sure any 2 setIntervals are isolated and will not affect each other?
Variable scope
Make sure that all of the variables involved are correctly scoped, and avoid adding any to the global scope unless it's completely unavoidable (this shouldn't be the case). This means you'll want to be using the var keyword whenever you declare any variables.
If your variables are correctly scoped to their respective setInterval calls then there's no danger of one affecting values in the other, even if you've used the same variable names.
Check your logic
If you're querying, and then modifying, the same set of elements on the page in both of them then they can't be independent, since changes in one of them will then be reflected in the next execution of the other one. Any shared logic, any use of global variables, etc. are all potential candidates for issues to be introduced.
Essentially you're looking for any overlap between the two, and then (hopefully) eliminating that. If it can't be eliminated then your two setIntervals can't be isolated, and you either have to accept that the two are linked or find another approach to solving the problem.

How are the intervals triggered?
Maybe you can try to call them in for example a click function:
$('<ELEMENT>').click( function() {
setInterval(function(){
},3000);
});
$('<ELEMENT>').click( function() {
setInterval(function () {
var checked = $('#status_table tr [id^="monitor_"]:checked');
if (checked.index()===-1 ||checked.length===0){
clearloop(bigloop);
$('#monitor').button('enable');
}else{
//$('#monitor').button('enable'); //enable the monitor button
(function loop(i) {
//monitor element at index i
monitoring($(checked[i]).parents('tr'));
//delay of 3 seconds
setTimeout(function () {
//when incremented i is less than the number of rows, call loop for next index
if (++i < checked.length) loop(i);
}, 3000);
}(0)); //start with 0
}
}, index*3000); //loop period
});

Related

Approach to check if callback was last fired?

I currently have a function which looks like that:
function update() {
buildUpdate(function(result) {
// send result to clients
});
}
This normally works correctly. However if I do something like:
// data state 1
update(); // this time, buildUpdate() won't take a long time
// do some work resulting in:
// data state 2
update(); // this time, buildUpdate() will take a long time
// and thus will finish after the third call
// do some work resulting in:
// data state 3
update(); // this time, buildUpdate() won't take a long time
As expected, the clients will receive three updates. However they are in the wrong order because the third call of update() did finish earlier than the second. From the clients point of view it looks like this:
Receives update calculated based on data state 1
Receives update calculated based on data state 3
Receives update calculated based on data state 2 (this update should not be sent)
Is there any design pattern or function which helps to avoid such a case?
Note: It doesn't matter if a client doesn't receive all updates. What matters is only that the last one received must be consistent with the current data state.
My idea was to generate on each invocation of update() a random ID. Afterwards I check in the callback whether its ID matches the last one that was generated. However the generation of the ID itself introduces a new async calculation and leads to much more code on each usage.
The easiest would probably be to add a callback
function update(callback) {
buildUpdate(function(result) {
// send result to clients
if (typeof callback == 'function') callback();
});
}
and do
update(function() { // when the first one finishes
update(function() { // run the second one
update(function() { // and when the second is finished, the third
update(); // and so on....
});
});
});
If you add the async middleware you would have more advanced methods available to deal with async behaviour.
My current approach works but is probably not the best solution.
Please submit an answer if you know a better way.
var outdated = function(f, cb) {
var counter = 0;
var finished = -1;
return function() {
var no = counter++;
a = [].slice.call(arguments);
a.unshift(function() {
if(no > finished) {
finished = no;
cb.apply(this, arguments);
}
});
f.apply(this, a);
};
};
Let's consider the following example:
var example = outdated(function(cb, a) {
setTimeout(function() {
cb(a);
}, a * 1000);
}, function(c) {
console.log('finished '+c);
});
example(1);
example(4);
example(2);
This will yield to the following output:
finished 1
finished 2
finished 4 is not being printed as it was called before finished 2 but ended after it.
To solve the actual problem as stated in the question, I would call the function like this:
var update = outdated(buildUpdate, function(result) {
// send update to clients
});
update();
// do some changes
update();

Pause execution in while loop locks browser (updated with fiddles)

I know my problem I just not sure how to resolve it. I have a custom domain and in a function call a while loop executes. In that loop i wanted an animation to occur in order.
So the first problem is that javascript by its nature executes every line thus item 2 starts before item 1 completes. Now the effect is so short that it "appears" to happen to all elements at once but in the debugger it is just looping one at a time.
Now my typical resolution would be to use SetTimeout() but that is causing the browser to lock. Reading this post (Trying to delay/pause/slow a while loop in jQuery) it makes sense that the browser is getting into an endless loop.
So how can I get a pause between element1 and element2 events? I thought perhaps to add a callback function to my custom domain but not sure if that will work as desired besides not being sure how to do it.
In the head of the page and read the comments for anything else I may be doing wrong or could do better.
$(document).ready(function ()
{
//pause long enough for person to visually take in page before starting
setTimeout(function () { PageLoadAnimation.onReady(); }, 1000);
});
My custom domain:
var PageLoadAnimation =
{
onReady: function ()
{
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
PageLoadAnimation.FlashElement();
},
BlackOutElements: function ()
{
$('#ParentContainer').children().hide();
},
FlashElement: function ()
{
//get array of all elements and loop till all are visible
var elementArray = $('#ParentContainer').children();
var $els = $('#PartialsContainer').children();
while (elementArray.length)
{
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
//if I put set timeout here is causes the infinite loop
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
//if I put by itself it no diff as the while loop continues regardless
//setTimeout(1500);
}
},
FlashBlast: function ($el)
{
//flash background
$el.fadeIn(200, function () { $el.fadeOut(200) });
}
}
I'm not sure if it isn't working or if I am doing something wrong so I created these fiddles:
Original Fiddle
With Johan Callbacks
Using is animating property
WARNING THIS ONE WILL HANG YOUR BROWSER!
I don't think I am checking the isAnimating property the way Johan had in mind??
ANSWER FOR THIS SITUATION. Hopefully it will help others.
setTimeout in a loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code I read here (http://javascript.info/tutorial/settimeout-setinterval) setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
A callback example that might help:
FlashBlast: function ($el, fadeInComplete, fadeOutComplete)
{
if(arguments.length === 3){
$el.fadeIn(200, function () {
fadeInComplete();
$el.fadeOut(200, fadeOutComplete);
});
}
}
Usage:
PageLoadAnimation.FlashBlast($el, function(){
//fadein complete
}, function(){
//fadeout complete
});
Another idea that might help:
isAnimating: false,
FlashBlast: function ($el)
{
var dfd = $.Deferred(),
that = this;
that.isAnimating = true;
$el.fadeIn(200, function () {
$el.fadeOut(200, function(){
dfd.resolve();
})
});
dfd.done(function(){
that.isAnimating = false;
});
}
Then make use of the private property isAnimating.
Finally, to know if an element is under an animation, you can use $el.is(':animated').
Hope this helps. Let me know if anything is unclear.

Keypress with delay, how to limit to only one execution of function call?

I'm setting up a form to lookup or create a new city record in a web app. I need the text field for the city to do the following:
Upon receiving text input, show a "spinner" indicating the computer is processing the input.
After the user has stopped typing for 1 second, send an ajax request to check the input.
Respond with the status (whether the input is valid).
I have this almost working but I have one major issue: the delay script I wrote works but after the delay is over it runs the rest of the function once per keyup that occurred during the delay. I would like it to only run once. Here's the code I'm working with (the console log events would be replaced with other function calls later):
$(function() {
locationSelector.initialize();
});
var locationSelector = {
initialize: function() {
locationSelector.bindCityName $('input#city_name')
},
city: {
status: function( status ) {
console.log( "Status message: " + status );
},
error: function(message) {
console.log("Error message: " + message );
}
},
bindCityName: function(selector) {
selector.on('keyup', function() {
locationSelector.city.status('loading');
var timer = null;
if(timer) {
window.clearTimeout(timer);
}
timer = window.setTimeout(
function() { locationSelector.getCity( selector ); },
1000
);
});
},
getCity: function(selector) {
if( selector.val() == "" ) {
locationSelector.city.error('Cannot be blank.');
} else {
console.log("AJAX REQUEST");
}
}
};
Why is the getCity function is being run once per keyup, and how can I fix that? Also, to be honest I'm very much a javascript novice, so I would appreciate any other suggestions on how to improve this code scaffold.
Thanks!
For starters, the variable declaration for timer needs to be in a scope that lasts from one key event to the next. As it is, it is a local variable that gets recreated from scratch on every key event so you're getting multiple timers going at once.
Move this:
var timer = null;
to the global scope or a higher scope that persists from one key event to the next. You can also define the timer variable as a property of location selector if you want:
var locationSelector = {
// other properties defined here
timer: null
}
Then, you can refer to it as locationSelector.timer. I'd also suggest that when your timer actually fires that you should set locationSelector.timer = null since a timer is no longer active.
It also looks like you'll need to clear the 'loading' status once you run the ajax.
And, your timer is set for 2 seconds, but you said you want to wait 1 second for no typing.
It looks like the problem is here:
var timer = null;
if(timer) {
window.clearTimeout(timer);
}
Timer gets set to null, so it never gets cleared. I'd suggest declaring timer at the top, above initialize, and then setting it to null only after you clear it, or after it gets executed.

Weird infinite loop bug in jquery recursive function

I have a strange issue on the project I'm working with. This changes an image source and a content of a div automatically.
I have coded a function, but it falls into infinite loop and page does not load (page is showing the loading page always).
These are the codes:
$.fn.extend({
changehaber: function(a){
$('#cokokunanlarcontent').fadeOut('slow',function() {
$('.jquerycokokunanlarbutton').attr('src','images/sabah/buton-pasif.png');
$('img[rel="'+a+'"]').attr('src','images/sabah/buton-aktif.png');
}).html($('#'+a).html()).fadeIn('slow');
return this;
}
});
function slidecokokunanlar() {
$('#cokokunanlarcontent').html($('#cokokunanlar1').html()).delay(3000).changehaber('cokokunanlar2').delay(3000).changehaber('cokokunanlar3').delay(3000).changehaber('cokokunanlar4').delay(3000).changehaber('cokokunanlar5').delay(3000);
slidecokokunanlar();
}
slidecokokunanlar();
What's the issue here, when this is executed, I want the function to work infinitely, but the page shows it's always loading. This is the console's output:
Uncaught RangeError: Maximum call stack size exceeded
Thanks in advance
You can't call a function from inside itself without blocking up the whole execution stack. By calling the function from inside itself, you're effectively preventing it from ever returning, and as Javascript is single-threaded, everything will grind to a halt!
Change your function to this:
function slidecokokunanlar() {
$('#cokokunanlarcontent').html($('#cokokunanlar1').html()).delay(3000)...
setTimeout(slidecokokunanlar, 0);
}
This allows for concurrent execution without blocking the UI, thus allowing your page to remain responsive.
See this article on "chunking" for more information on how this works.
This is because JavaScript doesn't have proper tail calls.
Your function calls itself at the end of itself forever. The first one never finishes and returns, nor does the second, nor do any of them until you run out of stack and explode.
You might try using setTimeout instead. See an example on jsFiddle.
EDIT You might not want to use 0 unless you really need it to be running continuously. Even using 100, you'd execute the function 10 times per second.
function foo(){
console.log('foo');
setTimeout(foo, 0);
}
foo();
Here's a cleaner way to do it.
var coko = $('#cokokunanlarcontent'); // cokokunanlarcontent
var cokos = $('[id^="cokokunanlar"]').not(coko); // cokokunanlar1..2..3 etc
var total = cokos.length; // total quantity
var i = 0;
var allow = true;
$('.jquerycokokunanlarbutton').attr('src','images/sabah/buton-pasif.png');
function slidecokokunanlar( isRestart ) {
if( !isRestart ) {
$('img[rel="' + cokos[i].id + '"]').attr('src','images/sabah/buton-aktif.png');
coko.html( cokos.eq(i).html() )
.fadeIn( 'slow' );
}
if( allow ) {
coko.delay( 3000 )
.fadeOut('slow', function() {
i = (++i % total);
slidecokokunanlar(); // recursively call with next index or 0
});
}
}
slidecokokunanlar(); // start it off
function restartSlider() {
allow = true;
slidecokokunanlar( true );
}
function stopSlider() {
allow = false;
}
stopSlider(); // to stop it
restartSlider(); // to restart it

Interrupt jQuery delay function

I am working on a way to autocomplete function to navigate through steps of a form. Here is the code that when 5 characters are entered into an input, it then moves to the next element. My delay is working great, but I don't have a way to stop it from completing if characters get deleted after 5 characters are entered. It just fires off the focus right after that no matter what has changed in the input.
Any thoughts?
var delay = (function(){
var timer = 0;
return function(callback, ms) {
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
$('input').keyup(function(){
if($(this).val().length == 5) {
delay(function(){ $("#saveForm2").focus(); }, 2000 );
}
})
If you're looking for an easy way to associate a timeout instance with an element, consider using jQuery's .data() method.
Something like this.
$('input').keyup(function() {
var $th = $(this);
var data = $th.data();
if(data.timeout === undefined) {
data.timeout = null;
}
if ($th.val().length == 5) {
clearTimeout(data.timeout);
data.timeout = setTimeout(function() {
$("#saveForm2").focus();
}, 2000);
} else {
clearTimeout(data.timeout);
}
});​
I don't think the way you were using the closure was quite right. I think you would need to assign the handler to the element inside the closure as well, so it has a local reference to the instance.
EDIT: Made a little more efficient with earlier stored reference to data().
Each time you call delay(), you clobber your timeout handle, which means that you can't manage it after the fact. It also means that you're going to fire off a request every time you hit 5 characters, if I read that correctly. Try something like this:
var delayTimer;
var nextField = function() { $("#saveForm2").focus(); }
$('input').keyup(function(){
clearTimeout(delayTimer);
if($(this).val().length >= 5) {
delayTimer = setTimeout(nextField, 2000);
}
})
That'll a) fire off no more than 1 request unless you wait more than 2 seconds between keystrokes, and b) will cancel any pending request if you drop back under 5 characters before the timeout expires. As a bonus, it won't create a whole mess of anonymous functions.

Categories

Resources