10 Nov

lazy loading functions in javascript

Lazy loading is the act of loading an asset (JavaScript, image, whatever) just before it’s needed, instead of at the beginning of the page load. This allows you to save bandwidth in cases where the asset will not be required at all, and also allows you to load up the page quicker because there is less JavaScript for the browser to interpret before it shows the screen.

A while ago I wrote lazy-loading for images into KFM. This time, it’s time to do it for JavaScript.

Here are two examples of lazy-loading methods for JavaScript. I wasn’t happy with either. In both cases, when a lazy-loaded function is to be called, the scripter must be careful about calling the function in case it’s not yet loaded, and the calling of the function is different to how you would do it in a normal script without lazy-loading applied.

What I wanted was a way to simply call a script (do_something('blah');) and have the script automatically load the function and when it’s loaded, run the function call again.

The way to do this is to replace the function do_something() with a stub. This stub loads up the real code, replaces itself with the code, then calls the replacement code with the original parameters (there, that’s the pattern in a nutshell).

So, to start with, your stub looks like this:

window.do_something=function(){
  var ps=arguments;
  x_kfm_getJsFunction("do_something",function(js){
    lazyload_replace_stub("do_something",js,ps);
  });
};

In the above, x_kfm_getJsFunction() is a simple Ajax function which calls kfm_getJsFunction() on the server-side with the first parameter, and when the result is returned, calls the second parameter as a callback, sending it the result. I won’t give the source for x_kfm_getJsFunction() as it’s framework-dependent. Each framework (prototype, mootools, dojo, etc.) has its own way of doing exactly that. My method uses a version of Sajax which I forked a long time ago.

On the server-side, the code is simple. Just keep each function in its own file in a directory called ‘functions’.

function kfm_getJsFunction($name){
  if(preg_replace('/[0-9a-zA-Z_.]/g','',$name)!='')return 'alert("no hacking!");';
  $js=file_get_contents('j/functions/'.$name.'.js');
  if(!$js)return 'alert("could not find function \''.addslashes($name).'\' on the server-side!");';
  return $js;
}

And when that’s retrieved, here’s the function that replaces the original function:

function lazyload_replace_stub(fname,js,ps){ // replace stub with function, then call function with original parameters
  eval(js);
  (eval(fname))(ps[0],ps[1],ps[2],ps[3]); // hacky method... replace when a better idea comes along
}

There final line calls the new function with an assumed four parameters. JavaScript is fine with this, so the function works. If there are more than four parameters you need to either change that line, or rethink your function’s parameters.

Now obviously, the do_something() code I pasted earlier is too large to be useful. I mean, we’re trying to save bandwidth, and here I advocate a large lump of code which pulls in a possibly smaller lump! That would be silly.

So, instead, what you do is to move all the functions you want lazy-loaded into their own files in your functions directory, and have the client generate the necessary lazy-load functions itself.

var llStubs=[];

llStubs.push('do_something'); // add do_something to the list of lazy-load functions
llStubs.push('another_function');
function non_lazyloaded_function(var1, var2){
...
}

var i,funcs=[],fname;
for(i=0;i<llStubs.length;++i){
  fname=llStubs[i];
  funcs.push('window.'+fname+'=function(){var ps=arguments;x_kfm_getJsFunction("'+fname+'",function(js){lazyload_replace_stub("'+fname+'",js,ps);});};');
}
eval(funcs.join("\n"));
funcs=null;
i=null;
fname=null;

I think that’s about it – you have the idea of it now.

So, some cases where you shouldn’t use lazy-loading.

  • When the function is needed upon load of the page. For example, if the function is part of your onload() initialisation. You can test this by adding alert(fname); to the lazyload_replace_stub() function – if anything is alerted upon loading the page, then you are lazy-loading functions unnecessarily.
  • When the function is called repeatedly in a rapid-fire manner. For example, if you have drag/drop code, the “ondrag” event should not be lazy-loaded, as it is called many times a second.
  • When the function returns a result. As noted, lazy-load is asynchronous, which means you cannot lazy-load a function which returns a value.
  • When the function performs an action which is needed somewhere later in the calling function. Obviously, an asynchronous call will take place at least 1ms after the call was made, and that’s well after the calling function would be finished.

In the cases above where the asynchronous nature of lazy-loading is the problem, you could simply change your AJAX method to use synchronous calling, but that makes your client feel sluggish.

One more thing – as this is supposed to save bandwidth, if you need 10 functions, you should bunch the requests into one single AJAX request instead of making 10 requests. My Sajax fork (Kaejax – yeah, I know) does that automatically. I don’t know if any others do it.

And another thing – functions that are to be lazy-loaded should be opened with window.function_name=function(){ instead of function function_name(){ in the retrieved code. This is to avoid any scoping problems JavaScript might throw up.