Basic Caching Strategy Javascript Class
I recently was asked to cache some data using memcached in node.js. Everything was easy to setup and the caching was working great with the cluster of memcached servers I was developing on. And as part of the application's testing we force shutdown a single memcached instance in the cluster to see how the application adapts. Unfortunately, we were seeing unacceptably negative forward facing response times. I later found out that there was a bug in the memcached library we were using. That has since been fixed, but even after that bug had been resolved we were seeing some of the cached requests that had been forced closed hanging and waiting for a response. We determined that it was most likely due to the fact the TCP socket never receives the FIN message from the memcached server, so the socket hangs until the timeout is met (around 60 seconds).
Either way, we needed a strategy that would protect the client from experiencing high response times. In comes my BasicCachingStrategy class. It allows us to define a standard API for simple get/set caching with the enforcement of a timeout.
Code is documented and will probably do a better job at explaining what it does than I will. ;)
- When overriding this class, you should always override the
get
andset
prototype methods. - When using this class, call
timeout_get
andtimeout_set
. Doing so enforces a timeout on theget
andset
methods. - This class is AMD/RequireJS/NodeJS/Browser ready
https://gist.github.com/mrlannigan/6314235
'use strict';
/**
* Basic Caching Strategy Class file
* @author Julian Lannigan <julian@jlconsulting.co>
* @since 22AUG2013
*/
(function () {
/**
* Timeout Error Class
* @param {String} msg Timeout error message
* @extends {Error}
*/
function TimeoutError(msg) {
Error.captureStackTrace && Error.captureStackTrace(this, this);
this.message = msg || 'Error';
}
TimeoutError.super_ = Error;
TimeoutError.prototype = Object.create(Error.prototype, {
constructor: {value: TimeoutError, enumerable: false},
name: {value: 'Timeout Error'}
});
/**
* Basic Caching Strategy
*/
function BasicCachingStrategy() {}
/**
* Default timeout length (ms)
* @type {Number}
*/
BasicCachingStrategy.prototype.timeout = 5000;
/**
* Extension of timeout property as a class property
* @type {Number}
*/
Object.defineProperty(BasicCachingStrategy, 'timeout', {
configurable: false,
enumerable: true,
writable: true,
value: BasicCachingStrategy.prototype.timeout
});
/**
* Extension of parent class for error class to use if a timeout occurs
* @type {Error}
*/
Object.defineProperty(BasicCachingStrategy, 'TimeoutError', {
configurable: false,
enumerable: true,
writable: true,
value: TimeoutError
});
/**
* Default `always miss` caching function (should always be overridden)
* @param {String} key Key for cache
* @param {Function} callback Callback (err, cached?, cachedValue)
* @return {BasicCachingStrategy}
*/
BasicCachingStrategy.prototype.get = function (key, callback) {
callback(null, false, null);
return this;
};
/**
* Default `always not cached` caching function (should always be overridden)
* @param {String} key Key for cache
* @param {Mixed} value Value to store
* @param {Function} callback Callback (err, cached?)
* @return {BasicCachingStrategy}
*/
BasicCachingStrategy.prototype.set = function (key, value, callback) {
callback(null, false);
return this;
};
/**
* Wrapper method for `get` with the addition of a timeout
*
* If you are writing a library to use this object, you should always
* call the timeout version of the applicable function. Override at
* your own risk.
*
* @param {String} key Key for cache
* @param {Function} callback Callback (err, cached?, cachedValue)
* @return {BasicCachingStrategy}
*/
BasicCachingStrategy.prototype.timeout_get = function (key, callback) {
var self = this,
timeout,
called = false;
timeout = setTimeout(function () {
called = true;
callback(new BasicCachingStrategy.TimeoutError('reached during get'), false, null);
}, this.timeout);
this.get(key, function () {
clearTimeout(timeout);
if (called) { return; }
callback.apply(self, arguments);
});
return this;
};
/**
* Wrapper method for `set` with the addition of a timeout
*
* If you are writing a library to use this object, you should always
* call the timeout version of the applicable function. Override at
* your own risk.
*
* @param {String} key Key for cache
* @param {Mixed} value Value to store
* @param {Function} callback Callback (err, cached?)
* @return {BasicCachingStrategy}
*/
BasicCachingStrategy.prototype.timeout_set = function (key, value, callback) {
var self = this,
timeout,
called = false;
timeout = setTimeout(function () {
called = true;
callback(new BasicCachingStrategy.TimeoutError('reached during set'), false);
}, this.timeout);
this.set(key, value, function () {
clearTimeout(timeout);
if (called) { return; }
callback.apply(self, arguments);
});
return this;
};
// AMD / RequireJS
if (typeof define !== 'undefined' && define.amd) {
define([], function () {
return BasicCachingStrategy;
});
}
// Node.js
else if (typeof module !== 'undefined' && module.exports) {
module.exports = BasicCachingStrategy;
}
// included directly via <script> tag
else {
this.BasicCachingStrategy = BasicCachingStrategy;
}
})();