# About Caching
Cache states
Objects requested from a cache can exist in one of three states: fresh, stale or nonexistent. These states are determined by the age and availability of the object on the cache object as it compares to the source. (on hardware cache limit states are: Modified, Exclusive, Shared, Invalid)
Fresh objects
Fresh objects are ready to be served in place of the original content. Freshness is determined by metadata which defines an expiration date or the maximum age of the object. Freshness can also be determined using an ageing factor based on the object’s last modification date.
Stale objects
Stale objects are cached objects that are no longer usable. Objects that passed age limit have to be re-cached from the source before they can be served by the cache object. After encountering a stale object, the cache object simultaneously caches the updated object from the source and sends it to the user.
Nonexistent objects
Objects that exist on the storage but not on the cache object follow the same procedure as stale objects. If an object exists on the cache object but not on the storage, then the cache object doesn’t serve the cached copy.
Drawbacks
Increased memory usage: increase the amount of memory that application uses. A problem when limited memory is available or if cache grows too large and begins to impact the performance
Cache invalidation: If caching data changes over time, be careful about cache invalidation. You need to make sure that you are invalidating the cache when the data changes, or you risk serving stale or incorrect data
Cache coherence: when multiple instances of application are running you need to make sure that your caches remain coherent. This means that all instances of application see the same cached data. Challenging to manage.
Cache warming: when using a cache that is not pre-populated with data, you need to make sure that you warm up the cache before it is used. This can be time-consuming, and if you don't do it correctly, performance issues arise while the cache is being populated.
Complexity: If you need to implement cache invalidation then complexity can make applications harder to understand and maintain.
```
function createCacheProxy(target) {
const cache = new Map();
return new Proxy(target, {
get(target, key) {
if (cache.has(key)) {
return cache.get(key);
}
const value = target[key];
if (typeof value === 'function') {
// If the property is a function, wrap it in a new function
// that caches the result and returns it on subsequent calls.
const cachedFn = function(...args) {
const result = value.apply(target, args);
cache.set(key, result);
return result;
};
return cachedFn;
}
// If the property is not a function, just return its value.
return value;
},
set(target, key, value) {
target[key] = value;
cache.delete(key);
return true;
},
deleteProperty(target, key) {
const result = delete target[key];
cache.delete(key);
return result;
}
});
}
// Example usage:
const myObject = {
expensiveMethod() {
console.log('Expensive method called.');
// Do some expensive calculation here.
return Math.random();
},
foo: 'bar'
};
const myObjectWithCache = createCacheProxy(myObject);
console.log(myObjectWithCache.expensiveMethod()); // Expensive method called. (random value)
console.log(myObjectWithCache.expensiveMethod()); // (cached random value)
console.log(myObjectWithCache.foo); // bar
```