📜 ⬆️ ⬇️

Reusable javascript proxy caching

Problem


It is no secret to anyone that performance remains one of the main indicators of the quality of a web application to this day. And, of course, any web developer spent more than one hour optimizing his application and achieving acceptable performance, both on the server and on the client side. Despite the fact that the hardware is day by day becoming more and more powerful, there are always bottlenecks that can be difficult to get around. With the advent of AJAX, HTTP requests have become “smaller” in terms of the amount of data received by the client, but their number has increased. Communication channels can be quite wide, but the connection time and the process of forming a response on the server can take considerable time. Caching query results on the client can significantly improve overall performance. Despite the fact that caching can be configured at the HTTP protocol level, it often does not meet the actual requirements.

Task


Our client caching system must meet the following requirements:
  1. The ability to implement the logic of cache management of any complexity;
  2. The ability to reuse in different applications;
  3. The possibility of transparent embedding in an existing application;
  4. Independence from the type of data and the way they are received;
  5. Independence from the method of storing cached data;

Existing application


Suppose we already have a working application that uses jQuery to get markup or data from the server via AJAX:

function myApp() { this.doMyAjax = function (settings) { settings.method = 'get'; settings.error = function (jqXHR, textStatus, errorThrown) { //handle error here } $.ajax(settings); } this.myServerDataAccess = function() { doMyAjax({ url: 'myUrl', success: function (data, textStatus, jqXHR) { //handle response here } }); } } 


Somewhere we call a method that applies for data:
')
 var app = new myApp(); app.myServerDataAccess(); 


Caching layer


We implement the simplest caching layer, which will consist of a proxy that controls access to the data, and a cache.

The interface that will be proxied consists of a single getData method. A fully transparent proxy simply delegates the call to its data source using the same interface:

 function cacheProxy(source) { var source = source; this.getData = function (request, success, fail) { source.getData(request, success, fail); } } 


Add a bit of logic to access the cache, which we will implement later:

 function cacheProxy(source, useLocalStorage) { var source = source; var cache = new localCache(useLocalStorage); this.getData = function (request, success, fail) { var fromCache = cache.get(request.id); if (fromCache !== null) { success(fromCache); } else { source.getData(request, function (result) { cache.set(request.id, result); success(result); }, fail); } } } 


When trying to get data, the proxy checks for its presence in the cache and returns it if it is there. If there are none, then it receives them using the source, puts it into the cache and gives it to the initiator of the request.

We implement the cache, with the possibility of placing data in Local Storage :

 function localCache(useLocalStorage) { var data = useLocalStorage ? window.localStorage || {} : {}; this.get = function (key) { if (key in data) { return JSON.parse(data[key]); } return null; } this.set = function (key, value) { if (typeof (key) != 'string') { throw 'Key must be of string type.'; } if (value === null || typeof (value) == 'undefined') { throw 'Unexpected value type'; } data[key] = JSON.stringify(value); } } 


Data is stored as key / serialized value of cached data.

Integration into an existing application


As you can see, the data access interfaces in the existing application and in the resulting proxy are different (do not think that this is an attempt to complicate your life, we did it intentionally for demonstration purposes). For integration, it is enough to write an adapter that implements the proxied interface and apply it:

 function applyCacheProxyToMyApp(app) { var app = app; app.old_doMyAjax = app.doMyAjax; var proxy = new cacheProxy(this, true); app.doMyAjax = function (settings) { proxy.getData({ id: settings.url }, settings.success, settings.error); } this.getData = function (request, success, fail) { app.old_doMyAjax({ url: request.id, success: success, error: fail }); } } var patch = new applyCacheProxyToMyApp(app); 


As you can see, we do not change a single line of the code of an existing application. Caching can also be painlessly turned off or thrown out if necessary. In order not to complicate the understanding, we will not implement the cache clearing algorithm, since it may depend on the specific requirements in a particular application.

Bonus


The resulting caching layer is easy to apply, for example, to resource-intensive, repetitive operations:

 function complicatedStuff(a, b) { return a * b; } function complicatedStuffAdapter(complicatedStuff) { var proxy = new cacheProxy(this, true); var source = complicatedStuff; this.complicatedStuff = function (a, b) { var result; proxy.getData({id: a.toString() + '_' + b, a: a, b: b}, function(res) { result = res; }); return result; } this.getData = function (request, success, fail) { success(source(request.a, request.b)); } } var p = new complicatedStuffAdapter(complicatedStuff); function test() { alert(p.complicatedStuff(4, 5)); } 


Finally


We considered only the approach to proxying any operations that your applications perform. Application options - many. From logging to implementing complex aspect-oriented algorithms, it all depends on your needs and imagination.

Source: https://habr.com/ru/post/131690/


All Articles