Skip to content

Instantly share code, notes, and snippets.

@chill-cod3r
Last active March 21, 2022 01:50
Show Gist options
  • Save chill-cod3r/8753f736cdc171527403dabf806f7c2b to your computer and use it in GitHub Desktop.
Save chill-cod3r/8753f736cdc171527403dabf806f7c2b to your computer and use it in GitHub Desktop.
typescript decorator to cache method response - rough example
// @experimentalDecorators
// @emitDecoratorMetadata
// use a more elegant cache in prod - something like node-cache or another cache
// this is a simplified example - for simplicity after the first method call we will simply set it to true.
// We also will not implement the cache expiration for simplicity.
// also note this is for cached aggregation of data that doesn't change, not "multi-tenant" data
let cacheObject: any = null;
function CacheResponse(timeInSeconds = 100) {
console.log('take note this part of this block runs on startup', { timeInSeconds });
/**
* this is the tough part in my opinion -
* target is the class that the method you're decorating is part of
* propertyName is the name of the function you're decorating
* descriptor is a reference to the runtime function + other metadata
* to get access to the function you're decorating is `descriptor.value` which you can see we depend on below
**/
return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
const decoratedMethod = descriptor.value;
// dynamically generate a cache key with the class name + the decorated method name - should always be unique
const cacheKey = `${target.constructor.name}#${propertyName}`
console.log('cache key: ', cacheKey);
// this can be set as async as long as it's decorated on an async function
// we don't specify specific arguments and instead rely on the "magic" javascript arguments keyword - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
// apply is necessary to understand at a high level as well - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
descriptor.value = async function () {
console.log('calling decorated method');
if (cacheObject) {
console.log('cache hit - returning cache object');
return cacheObject;
}
console.log('cache miss - calling actual method and caching for next time');
// the "this" argument is correct for current scope because this is an anonymous function keyword, _not_ an arrow function
// arguments will be all arguments
cacheObject = await decoratedMethod.apply(this, arguments);
return cacheObject;
};
}
}
class ClassWithCachingExample {
responseCount = 0;
static CACHE_TIME_SECONDS = 60 * 60;
@CacheResponse(ClassWithCachingExample.CACHE_TIME_SECONDS)
async doStuff() {
return new Promise(resolve => {
// increment response count to show initial call is not cached
this.responseCount += 1;
// arbitrary 5 second delay to show that after the first call, the rest will be cached
setTimeout(() => resolve(this.responseCount), 5000);
});
}
}
const instance = new ClassWithCachingExample();
(async () => {
console.log('first call');
console.log(await instance.doStuff()); // 1 after a 5 second delay
console.log('the rest of the calls');
console.log(await instance.doStuff()); // 1 after no delay other than "awaited promise"
console.log(await instance.doStuff()); // 1 after no delay other than "awaited promise"
console.log(await instance.doStuff()); // 1 after no delay other than "awaited promise"
console.log(await instance.doStuff()); // 1 after no delay other than "awaited promise"
console.log(await instance.doStuff()); // 1 after no delay other than "awaited promise"
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment