JavaScript: Don’t spam your server, Debounce and Throttle

If you make server call-backs or update the browser GUI based on user interaction you can end up doing far more updates than necessary…

In fact on one occasion, during Illyriad-beta testing, we started tripping our server’s dynamic IP request rate throttling and banning our own IP address – D’oh!

Common situations where over responding to events are mousemove, keypress, resize and scroll; but there are a whole host of situations where this applies.

We update our pages dynamically via ajax, based on user clicks around the user interface. In this situation if the user makes several page navigation requests in quick succession – the only important one to be made is the last, as for all the previous ones the data will be thrown away. So why make the requests in the first place? It’s just unnecessary server load.

Equally, if a user is moving an mouse around a map and you are updating UI coordinates for every pixel change the mouse makes, it potentially a huge number of DOM updates far beyond the perception of a user; for no real benefit, but it will cause a degradation in performance.

Initially, we controlled the situation using complex setups of setTimeout clearTimeout and setInterval. However, as the various places we made use of this became more varied, with different sets of needs, the code became more and more complex and difficult to maintain.

Luckily, it was around this time we discovered Ben Alman’s jQuery plugins debounce and throttle; which are extremely powerful – yet simple to use of and cover a whole host of permutations.

Throttling

Using his  jQuery $.throttle, you can pass a delay and function to $.throttle to get a new function, that when called repetitively, executes the original function no more than once every delay milliseconds.

Throttling can be especially useful for rate limiting execution of handlers on events like resize and scroll. Check out the example for jQuery throttling.

Debouncing

Using his jQuery $.debounce, you can pass a delay and function to $.debounce to get a new function, that when called repetitively, executes the original function just once per “bunch” of calls, effectively coalescing multiple sequential calls into a single execution at either the beginning or end.

Debouncing can be especially useful for rate limiting execution of handlers on events that will trigger AJAX requests. Check out the example for jQuery debouncing.

In fact its well worth checking out all Ben Alman’s jQuery Projects as they are full of little wonders, that you soon are sure how you did without them!

(Fix) Memory Leaks: Ajax page replacement

Resolving memory issues with HTML replacement and Ajax

In Illyriad we do very large numbers of ajax requests using jQuery over a players session. Some of these are pure data requests, but many of them are navigational HTML page replacements.

A simple replacement seems to leak memory in various browsers:

[code lang=”js”]
$(‘#ElementToReplaceContents’).html(htmlToReplaceWith);
[/code]

Even calling the jQuery function .empty() before the replacement doesn’t seem to help in all cases. To fix this we have created a clean up function which is a common or garden “overkill” variety which seems to do the trick. Usage below:

[code lang=”js”]
function ChangeLocationSucess(data, textStatus, XHR) {
if (XHR.status == 200) {
var div = $(‘#ElementToReplaceContents’);
DoUnload(); // More on this further down
div.RemoveChildrenFromDom(); // Clean up call
div.html(data); // HTML replacement
}

[/code]

Clearing the memory from the existing HTML is done with the clean up code below:

[code lang=”js” title=”Added: RemoveChildrenFromDom jQuery plugin”]
(function( $ ){
$.fn.RemoveChildrenFromDom = function (i) {
if (!this) return;
this.find(‘input[type="submit"]’).unbind(); // Unwire submit buttons
this.children()
.empty() // jQuery empty of children
.each(function (index, domEle) {
try { domEle.innerHTML = ""; } catch (e) {} // HTML child element clear
});
this.empty(); // jQuery Empty
try { this.get().innerHTML = ""; } catch (e) {} // HTML element clear
};
})( jQuery );
[/code]

Some pages have extra resources cached and extra elements with wired up events. For these we have introduced a DoUnload function which also gets called before the replacement. This runs through the list of on page clean up functions and calls them one by one:

[code lang=”js” title=”Extra clean up system”]
var unloadFuncs = [];
function DoUnload() {
while (unloadFuncs.length > 0) {
var f = unloadFuncs.pop();
f();
f = null;
}
}
[/code]

We create these unload functions on the page that has any extra clean up to do by using the following code; which adds clean up functions to the unloadFuncs array above. This code looks like the code below:

[code lang=”js” title=”On page clean up registration”]
var unload = function() {
OnPageCachedResources.clear(); // Clear array of cached resources
OnPageCachedResources = null;
$("#OnPageElementWithWiredUpEvents")
.unbind()
.undelegate(); // Remove attached events
}
unloadFuncs.push(unload);
[/code]

Obviously this varies from page to page and most of our pages don’t need it – at which point the DoUnload function just returns with out doing work.

This coupled with the Fix for IE and JQuery 1.4 Ajax deal with most of the regular unexpected leaks.

(As an aside: When replacing HTML with Ajax, do not wire up events using onclick=”” etc in the HTML or this will also leak. Use event binding from script – a third party library like jQuery, Prototype or Dojo will make this very straight forward)

(Fix) Memory Leaks: IE and JQuery 1.4 Ajax

While testing Illyriad we found that over time the browsers IE7 and IE8 leak memory on Ajax calls using JQuery 1.4.

If you make a lot of ajax calls without changing page this can build up quite quickly and start causing issues. This is caused by the onreadystatechange event not being detached and IE not garbage-collecting the associated memory.

To fix this, locate these lines in the source JQuery file; already kindly annotated with “Stop memory leaks”:

[code lang=”js” firstline=”6019″ title=”Original jquery-1.4.4.js”]
// Stop memory leaks
if ( s.async ) {
xhr = null;
}
[/code]

Add in these extra lines to change them to the following (we detach the abort event also for good measure):

[code lang=”js” firstline=”6019″ title=”Modified jquery-1.4.4.js”]
// Stop memory leaks
if ( s.async ) {
try {
xhr.onreadystatechange = null;
xhr.abort = null;
} catch (ex) { };
xhr = null;
}
[/code]

Don’t forget to minify your jquery file before including it. [We prefer using UglifyJS]

Also rename it slightly so users with cached versions pick up the new file

e.g. jquery-1.4.4a-min.js

(Fix) Javascript: Avoiding “console is undefined”

Testing if a function or object is defined can sometimes cause as many errors as not doing so. What to do?

When developing JavaScript using either Firebug in Firefox or Chrome’s built-in console you will probably at some point be using console.log either for debugging messages or for exception information.

However, when you come to run this code in Internet Explorer, it throws an exception “console is undefined”:

[code lang=”javascript”]
try {
… // code throwing error
} catch (e) {
console.log(e); // throws script error "console is undefined"
}
[/code]

“A-ha” you think; “easily solved will just test if its defined before use”. However things are not so straight forward:

[code lang=”javascript”]
try {
… // code throwing error
} catch (e) {
if (console && console.log) // throws script error "console is undefined"
console.log(e);
}
[/code]

Even though now you are testing to see if it exists before use, you still get:

Console is undefined

While this would normally work for attributes of objects, globally scoped variables work differently. Luckily, all variables in global scope will be declared as attributes on the global window variable. This enables you to test for existence on the window.

Here we’ve wrapped the test up in a LogException function to ease changing of logging methodologies:

[code lang=”javascript”]
function LogException(e) {
if (window.console // check for window.console not console
&& window.console.log) window.console.log(e);
// Other logging here
}

try {
… // code throwing error
} catch (e) {
LogException(e); // works fine
}
[/code]

Note: Its isn’t exclusive to Internet Explorer, it just most visible there; so checking existence of globally scoped functions and variables against window rather than just by name is good practice!