Sometimes, we may just want to cache a method return value for sometime. Typescript come really handy. We can create a custom decorator and drop it on a method and be done with it. decorators @Cache({ : }) method2(){ ( { setTimeout( resolve( .random()), ); }); } duration 10000 return new Promise => resolve => () Math 1000 "A Decorator is a special kind of declaration that can be attached to a , , , , or . Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration." class declaration method accessor property parameter Let's start simple! @Cache() method1(){ .random(); } return Math { originalFunc: ; { originalFunc = descriptor.value; descriptor.value = { .log( , ()); .log( ) }; }; export ( ) function Cache let Function return ( ) function target: any, propertyKey: string, descriptor: PropertyDescriptor ( ) function console 'time: ' new Date console 'why is this doing??' We took the which contains the function/method that has the decorator. So this is our original function. We put it in a variable aka (what an original name huh!) and provide a new function! So when called instead of our original function this function will be called instead! descriptor.value originalFunc Of course, we can also call the original function here. { originalFunc: ; { originalFunc = descriptor.value; descriptor.value = { .log( , ()); originalFunc(); }; }; } export ( ) function Cache let Function return ( ) function target: any, propertyKey: string, descriptor: PropertyDescriptor ( ) function console 'time: ' new Date return So only difference now is this decorator shows the timestamp before running the original function (Original function is just returning a random number). Let's actually cache the value! { originalFunc: ; value: any; { originalFunc = descriptor.value; descriptor.value = { (value){ .log( ) value; } .log( ) value = originalFunc(); value; }; }; } export ( ) function Cache let Function let return ( ) function target: any, propertyKey: string, descriptor: PropertyDescriptor ( ) function if console 'from cache' return console 'caching' return We added a new variable called . We check if value is defined, if it is, return the value. If not, call the original function to get the value and return it. value Now, we have a problem this will cache indefinitely. We may not want to cache it forever. Instead it will be better if we can decide for how long we want it to cache. Let's add an interface for the parameters. For now, we have only duration but we can add more if we want to. We have also created an object with default values so that if not passed in, we can give some default values. interface CacheOptions { duration?: number } { defaultValues: Partial<CacheOptions> = { : , } params = { ...defaultValues, ...params }; ... export // we may add additional parameters here export ( ) function Cache params: CacheOptions = {} const duration 3000 We now need to calculate due time. ... let originalFunc: ; value: any; cacheUntil: ; { originalFunc = descriptor.value; descriptor.value = { now = (); (value && cacheUntil && cacheUntil > now) { .log( ); value; } .log( ) value = originalFunc(); cacheUntil = (now.getTime() + params.duration); value; }; }; } Function let let Date return ( ) function target: any, propertyKey: string, descriptor: PropertyDescriptor ( ) function const new Date if console "from cache" return console 'caching' new Date return In addition to checking value, we also are checking if cacheUntil is ahead of now. If so, returning the cached value. Otherwise, after getting the new value, we calculate the cacheUntil value. As you can see, now it's not caching indefinitely. It expires every 3 seconds. How about observables/promises? Before adding more code, I will put the caching logic into an inline function so that we can call in multiple places without leaving the function scope. ... const cacheValue = { .log( ); cacheUntil = (now.getTime() + params.duration); value = val; }; { originalFunc = descriptor.value; descriptor.value = { now = (); (value && cacheUntil && cacheUntil > now) { .log( ); value; } .log( ) value = originalFunc(); cacheValue(value, now); value; }; }; ... ( )=> val, now console "caching " new Date return ( ) function target: any, propertyKey: string, descriptor: PropertyDescriptor ( ) function const new Date if console "from cache" return console 'caching' return When we called the original function, the return type could be a value (we assumed that it will be a value so far), it can also be promise or an observable. ... const result = originalFunc(); (result Observable){ funcType = ; result.pipe( tap( { cacheValue(val, now); })); } ( result ){ funcType = ; result .then( { cacheValue(value, now); value; }); } { funcType = ; cacheValue(result, now); result; } ... if instanceof 'observable' return => val else if instanceof Promise 'promise' return => value return else 'value' return We check the instance of the result to determine the type of it. I added a new variable called funcType as we will need it, when we are returning the cached value. Based on each type, we cached the value and return the value in a consistent way. And for returning the cached value: ... if (value && cacheUntil && cacheUntil > now) { .log( ); (funcType){ : (value); : .resolve(value); : value; } } ... console "from cache" switch case "observable" return of case "promise" return Promise default return Probably, in a real scenario will throw an error related to keyword. Let's replace it as . If our methods have parameters, we can use instead. originalFunc() this originalFunc.apply(this); originalFunc.apply(this, args); Finally: interface CacheOptions { duration?: number } { defaultValues: Partial<CacheOptions> = { : , } params = { ...defaultValues, ...params }; originalFunc: ; value: any; cacheUntil: ; funcType: string; cacheValue = { .log( ); cacheUntil = (now.getTime() + params.duration); value = val; }; { originalFunc = descriptor.value; descriptor.value = { now = (); (value && cacheUntil && cacheUntil > now) { .log( ); (funcType){ : (value); : .resolve(value); : value; } } result = originalFunc.apply( ); (result Observable){ funcType = ; result.pipe( tap( { cacheValue(val, now); })); } ( result ){ funcType = ; result .then( { cacheValue(value, now); value; }); } { funcType = ; cacheValue(result, now); result; } }; }; } export // we may add additional parameters here export ( ) function Cache params: CacheOptions = {} const duration 3000 let Function let let Date let const ( )=> val, now console "caching " new Date return ( ) function target: any, propertyKey: string, descriptor: PropertyDescriptor ( ) function const new Date if console "from cache" switch case "observable" return of case "promise" return Promise default return const this if instanceof 'observable' return => val else if instanceof Promise 'promise' return => value return else 'value' return { callMethod1(){ .log( .method1()); } callMethod2(){ .method2().then( .log); } callMethod3(){ .method3().subscribe( .log) } @Cache() method1(){ .random(); } @Cache({ : }) method2(){ ( { setTimeout( resolve( .random()), ); }); } @Cache() method3(){ ( .random()) .pipe(debounceTime( )) } } export class AppComponent console this this console this console return Math duration 10000 return new Promise => resolve => () Math 1000 return of Math 1000 Caution When we add parameters to our methods, even though the arguments change, the cached value will not change. We may need to add a hashing method to differentiate the different calls so that we can skip caching. You can see the on here. demo