I am pretty experienced in Javascript but I have come across a weird issue in developing a web app where the script just isn't behaving as it should be. Oddly (inexplicably, in fact) I don't experience this issue when I am using my offline development copy or after logging in. However, when the user is already logged in and they refresh the page, they come across this issue.
Here is the affected method (this is literally the entire thing):
_setLiveSyncTimeout: function (firstTestCall) {
if (firstTestCall) {
console.log('set next')
window.setTimeout(function () {
console.log(200, 'a');
window.setTimeout(function () {
console.log(400, 'b');
}, 200);
}, 200);
window.setTimeout(function () {
console.log(400, 'c');
}, 400);
}
this._liveSyncTimeout = setTimeout(_(this.liveSyncNow).bind(this), this._liveSyncPeriod);
}
I noticed that this.liveSyncNow didn't seem to be getting called every time I would have expected it too, so I added in the if statement above to debug. Weirdly, when you pass in true, the first timeout ('a') will run, however, the 'b' and 'c' do not. Since the result of setTimeout is not stored in these cases, it should be literally impossible to cancel them but, for no apparent reason, they simply do not run. In the same case that 'b' and 'c' do not run, the timeout for this.liveSyncNow does not run either.
From tests it appears that when this function runs, all of the timeouts previously created in it get cancelled. The reason that the 'a' timeout does run is that there is a 200ms gap between the call where it is created and the next one.
Edit
This occurs in at least Chrome and Firefox.
Edit 2
Here is a more simple example of the issue. This is part of the definition of a Backbone model (you don't need to worry about that though). This is literally the entire function.
initialize: function () {
var a = setInterval(function () {
console.log('test')
}, 50);
}
The setInterval runs three times ('test' appears in the console three time) and then just stops.
I experience this issue (unsurprisingly) as a totally unrelated piece of code was incorrectly cancelling my timeouts. This happened because I was using a modded version of setTimeout, which returned its own incrementally-assigned integer but I did not use the correct modded version of clearTimeout and instead used the original window.clearTimeout. As a result my good timers were incorrectly being cancelled.
I found this issue and tracked down the dodgy piece of code by overwriting clearTimeout and finding out who called it, when, and with what id.
I have attempted to prevent this from happening in future by assigning ids in my modded setTimeout decrementally, below 0.
Related
I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.
I'm currently working with a large pre-existing codebase that may have one or more setInterval timers running all the time, from various plug-ins or libraries. This basically makes it impossible to try to use Break on Next to debug what happens when I click on an element.
Problem: As soon as I click Break on Next, the browser debugger (tried with Firebug and Chrome) stops in one of the setInterval functions before I have a chance to interact with the page to really debug the event that I want.
Specific problem: I have checkboxes that stay unchecked when unchecked, no matter how many times I click on them. I've removed the ID and class names as well to no avail and appear to have no event handlers attached.
Note: not using jQuery
This may brake other things, but what if you try to monkey-patch-out those calls like this:
window.setInterval = function() { console.log("setInterval", arguments); };
window.setTimeout = function() { console.log("setTimeout", arguments); };
If you find that some of timeouts/intervals are actually needed to reproduce your problem, you could try letting them through. Than the code could be something like:
window.oldSetTimeout = window.setTimeout;
window.setTimeout = function() {
if (arguments[0] == "code you want to allow") {
oldSetTimeout.apply(null, arguments);
} else {
console.log("setTimeout", arguments);
}
};
Note: I wouldn't be suprised it monkey-patching setTimeout does not work cross-browser, but it works on FF 18.0
My ASP.NET MVC page uses JavaScript/jQuery to poll my database every second.
This is working but I want to make sure that, if there is a delay, my timer handler won't get called again before it has returned.
In there any trick to this other than storing the timer ID in a global variable, clearing the timer in my handler, and restarting it when my handler is done.
NOTE: I realize every second seems frequent but this code is polling my server after submitting a credit card payment. Normally, it will only run for a second or so, and I don't want the user to wait any longer than necessary.
Polling every second? That's quite heavy!
That aside, you won't have this issue when setTimeout is used instead of setInterval. The latter ensures that a piece of code is run x times given a interval, while the former ensures that there's a delay of at least x milliseconds.
function some_poller() {
$.ajax({
url: '/some_page',
success: function() {
setTimeout(some_poller, 1000);
},
error: function() { // Also retry when the request fails
setTimeout(some_poller, 1000);
}
});
}
// Init somewhere
some_poller();
Not really, although I wouldn't recommend using a global variable. Stick it inside some function.
But are you really sure you need to poll every second? That's an extremely chatty interface.
In my personal experience a "global", (inside of the root function), variable works very well in this instance so that you can control when to clear and restart. If the response is really as quick as you say, this shouldn't cause too much overhead, (clearing/resetting), and will allow to account for these type of situations.
recently I've encountered a problem with IE. I have a function
function() {
ShowProgress();
DoSomeWork();
HideProgress();
}
where ShowProgress and HideProgress just manipulate the 'display' CSS style using jQuery's css() method.
In FF everything is OK, and at the same time I change the display property to block, progress-bar appears. But not in IE. In IE the style is applied, once I leave the function. Which means it's never shown, because at the end of the function I simply hide it. (if I remove the HideProgress line, the progress-bar appears right after finishing executing the function (more precisely, immediately when the calling functions ends - and so there's nothing else going on in IE)).
Has anybody encountered this behavior? Is there a way to get IE to apply the style immediately?
I've prepared a solution but it would take me some time to implement it. My DoSomeWork() method is doing some AJAX calls, and these are right now synchronous. I assume that making them asynchronous will kind of solve the problem, but I have to redesign the code a bit, so finding a solution just for applying the style immediately would much simplier.
Thanks rezna
Synchronous requests block the entire UI, which is horrible. You are right in your assumption, but if you want to continue down this path to the inner circles of $hell, try this:
function () {
ShowProgress();
window.setTimeout(function () {
DoSomeWork();
HideProgress();
}, 0);
}
Note that I have not tested this. Try playing with the time-out value (currently 0) if you still do not see anything.
Try throwing the HideProgress method into a setTimeout. For example:
function() {
ShowProgress();
DoSomeWork();
setTimeout(HideProgress,0);
}
Even though the delay is supposedly 0 milliseconds, this will have the net effect of throwing the HideProcess method to the end of the queue and may give the browser the breathing room it needs.
[Edit] I should have mentioned that this method does have a drawback if you invoke this method very often and rapidly: a race condition. You could end up with a previous timeout executing while another DoSomeWork() is executing. This will happen if DoSomeWork take a very long time to finish. If this is a risk, you may want to implement a counter for your progress bar and only execute HideProgress if the counter that started it is the same as the counter's present value.
I'm using jQuery to change the HTML of a tag, and the new HTML can be a very long string.
$("#divToChange").html(newHTML);
I then want to select elements created in the new HTML, but if I put the code immediately following the above line it seems to create a race condition with a long string where the changes that html() is making may not necessarily be finished rendering. In that case, trying to select the new elements won't always work.
What I want to know is, is there an event fired or some other way of being notified when changes to html() have finished rendering ? I came across the jQuery watch plugin, which works alright as workaround but it's not ideal. Is there a better way ?
As a commenter already mentioned, JavaScript is single threaded, so you can't get race conditions.
What may trip you up however, is the fact that the UI will not update itself based on JavaScript, until a thread is finished. This means that the entire method must finish, including all code after you call html(...), before the browser will render the content.
If your code after calling html(...) relies on the layout of the page being recalculated before continuing, you can do something like this:
$("#divToChange").html(newHTML);
setTimeout(function() {
// Insert code to be executed AFTER
// the page renders the markup
// added using html(...) here
}, 1);
Using setTimeout(...) with a time of 1 in JavaScript defers execution until after the current JavaScript code in the calling function finishes and the browser has updated the UI. This may solve your problem, though it is difficult to tell unless you can provide a reproducible example of the error you're getting.
use .ready jQuery function
$("#divToChange").html(newHTML).ready(function () {
// run when page is rendered
});
It's 7 years latter and I just ran into a scenario exactly like the one #mikel described, where I couldn't avoid a "timer based solution". So, I'm just sharing the solution I developed, in case anyone out there is still having issues with this.
I hate having setTimeouts and setIntervals in my code. So, I created a small plugin that you can put where you think it's best. I used setInterval, but you can change it to setTimeout or another solution you have in mind. The idea is simply to create a promise and keep checking for the element. We resolve the promise once it is ready.
// jquery.ensure.js
$.ensure = function (selector) {
var promise = $.Deferred();
var interval = setInterval(function () {
if ($(selector)[0]) {
clearInterval(interval);
promise.resolve();
}
}, 1);
return promise;
};
// my-app.js
function runWhenMyElementExists () {
// run the code that depends on #my-element
}
$.ensure('#my-element')
.then(runWhenMyElementExists);