The loading of resources on-demand is an important concept in non-trivial web -sites and -applications. Visitors to a given site often arrive via a link in a search engine, without knowing what to expect, and then leave within seconds because the site does not fit their requirements. The implications of this are significant because, without careful design and implementation, the user agent will start retrieving its content and supporting-functionality in ignorance of the visitor's imminent departure.
This pattern of activity is redundant and highly inefficient, in that it consumes bandwidth that is wasted should the visitor move on within seconds. It is especially unfavourable if the site provider pays for that bandwidth. Moreover, it may cause a site to give the impression of of being sluggish and unresponsive should the content and functionality take appreciable time to arrive on the client machine.
Loading on Demand
Compounding the Overhead
Using a Loading Manager
The Case for Method-Call Interception
Taken to its logical extreme, this approach suggests that one can implement a site such that user agents retrieve only the barest minimum from the server initially – sufficient to give users an adequate impression of a site's content and purpose – after which it loads all other functionality only on-demand. This offers great benefits in terms of efficiency and performance; sites that take the on-demand approach will, initially, arrive faster on the user's machine, and, on average, will be far less costly in terms of bandwidth to deliver because those visitors that arrive and then leave without exploring the site will draw only minimal resources from the server.
However, the traditional approaches to on-demand loading offset these savings because the mechanisms that trigger and perform the loading of resources when they are needed incur significant overheads. This page examines these drawbacks, and shows that using method-call interception (MCI) to implement on-demand loading will deliver all the benefits with minimal overheads.
A variety of techniques can be used to retrieve resources on demand, such as XMLHTTPRequest (XHR), or the injection of script, link or image elements into the HTML for a given page. WebSockets also hold considerable promise here, as they enable a system's client-side to maintain a single fast-connection with the server as a delivery pipeline through which a variety of different data-types may be acquired with very low network-overhead.
Example 1 illustrates this. Here the initial statements in someMethod create a script element, sets its src attribute to the URL of the desired code, and then inserts the element into the head element for the page in question. It does not play its central role until after that, meaning that the loading code is disjunctive – it does not relate directly to the rest of the function body, and this serves to clutter the function, thus harming readability and comprehension.
Sadly, the problems do not stop there. The AJS_Validator Motivation page shows that one solution to the overheads that tradtional approaches to design-by-contract incur is to use conditional comments. This is because, typically, we want our validation framework in place only during development of a given system, and so conditional comments allows us to remove the code that checks arguments, return values, and system state from the deployed version.
This gives the benefits of method-call validation during development, without its performance overhead when the system is out in the wild, but we have no such recourse when using on-demand loading, as the benefit of that technique is realised only when a system is deployed. Moreover, the mechanism that Example 1 demonstrates is simplistic. Without additional logic, each subsequent call to the function will see the loading code execute once again. It would be far better if we could to remove the loading code once it has done its job, thus relieving the application of such dead-weight.
This issue is deeper than it appears initially. We may have a number of functions that all contain identical statements that effect retrieval of the same resource, but as soon as any instance of those statements execute, its siblings at all locations in the system are also rendered redundant. Thus we need a way to disable the loading code in all of these locations en masse.
Still further, the loading trigger is 'bound' to the function. In Example 1, someMethod will always load a file called SomeCode.js. This impinges on the reusability of someMethod. If it form parts of a library that we employ in a number of applications, then we are stuck with always loading SomeCode.js, irrespective of the application in which SomeeEthod finds itself operating.
All of these points run contrary to the very aim of loading on-demand, which is to pay only for what one uses.
Clearly, we can manage the repetition of loading code across a system by implementing some form of 'loading manager', where a single call to that component triggers the loading of perhaps a number of resources - one could pass an array of URLs - and Example 2 illustrates this idea.
This moderates a number of the drawbacks explored above, however, we are still stuck with the non-cohesive call to the loading manager, so the code for the functions concerned still suffers from a degree of clutter. Moreover, once a resource has been retrieved, the system must still execute the calls to the manager, even though they are now redundant. This is ugly and inelegant.
The points made above are the motivation for AJS_ODL (AspectJS On-Demand Loading), which provides the benefits of loading on-demand with only minimal overhead. We can create a special kind of object that describes what should be loaded, the method by which that resource should be retrieved, and the names of the methods that should trigger the loading of the resource(s) indicated. We can then pass that object to AJS_ODL, which will attach a prefix function to the methods in question, such that subsequent calls to those methods cause the prefix to initiate retrieval of the required resources.
This absolves us from having to implement the various loaidng mechanisms ourselves, as that functionality is concentrated inside AJS_ODL. Moreover, using method-call interception allows us to trigger resource retrieval without cluttering the methods that trigger the loading operation. In Example 1 above, someMethod can remain entirely free of the script-manipulation logic, or indeed any such clutter because the loading-code is applied externally to the method body. Casual observers of such code can concentrate solely on what the method does and how it does it, without even considering whether critical resource-dependencies are satisfied or not (or even the nature of the mechanism that satisfies those dependencies).
Furthermore, once a given loading prefix has executed, it can be detatched from the method to which it forms an affix. This means that subsequent calls to the method do not even trigger a redundant call to the loading manager – from the point of view of the application and the method, life goes on as if nothing had changed (except that a critical resource is now available, slightly less memory is in use, and performace is slightly better). We also lose the binding effect of the traditional approaches. Given that a loading prefix is applied externally to a given function, we can change the identity of the resource that the prefix loads on a per-application basis. Revisiting Example 1, someMethod would be stuck no longer with always loading SomeCode.js.
Still further benefits are available given that the AJS object can attach multiple prefixes to a given method. This means that a given part of an application can apply its own, very specific loading-requirements to the same method in happy ignorance of the fact that other parts of the application do precisely the same thing. Each loading-prefix for a given method will execute transparently from the point of view of the other prefixes, thus enabling the execution of a given method to cause the retrieval of a large number of different resources.
To learn how to use AJS_ODL, go now to the user guide.