Last active
March 21, 2022 01:50
-
-
Save chill-cod3r/8753f736cdc171527403dabf806f7c2b to your computer and use it in GitHub Desktop.
typescript decorator to cache method response - rough example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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