- 
      
- 
        Save dsherret/cbe661faf7e3cfad8397 to your computer and use it in GitHub Desktop. 
| /** | |
| * Caches the return value of get accessors and methods. | |
| * | |
| * Notes: | |
| * - Doesn't really make sense to put this on a method with parameters. | |
| * - Creates an obscure non-enumerable property on the instance to store the memoized value. | |
| * - Could use a WeakMap, but this way has support in old environments. | |
| */ | |
| export function Memoize(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) { | |
| if (descriptor.value != null) { | |
| descriptor.value = getNewFunction(descriptor.value); | |
| } | |
| else if (descriptor.get != null) { | |
| descriptor.get = getNewFunction(descriptor.get); | |
| } | |
| else { | |
| throw "Only put a Memoize decorator on a method or get accessor."; | |
| } | |
| } | |
| let counter = 0; | |
| function getNewFunction(originalFunction: () => void) { | |
| const identifier = ++counter; | |
| return function (this: any, ...args: any[]) { | |
| const propName = `__memoized_value_${identifier}`; | |
| let returnedValue: any; | |
| if (this.hasOwnProperty(propName)) { | |
| returnedValue = this[propName]; | |
| } | |
| else { | |
| returnedValue = originalFunction.apply(this, args); | |
| Object.defineProperty(this, propName, { | |
| configurable: false, | |
| enumerable: false, | |
| writable: false, | |
| value: returnedValue | |
| }); | |
| } | |
| return returnedValue; | |
| }; | |
| } | |
| // ------------ ES6 VERSION ---------- | |
| export function Memoize(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) { | |
| if (descriptor.value != null) | |
| descriptor.value = getNewFunction(descriptor.value); | |
| else if (descriptor.get != null) | |
| descriptor.get = getNewFunction(descriptor.get); | |
| else | |
| throw new Error("Only put a Memoize decorator on a method or get accessor."); | |
| } | |
| const weakMap = new WeakMap<object, Map<string, unknown>>(); | |
| let counter = 0; | |
| function getNewFunction(originalFunction: (...args: any[]) => void) { | |
| const identifier = counter++; | |
| function decorator(this: any, ...args: any[]) { | |
| let propertyValues = weakMap.get(this); | |
| if (propertyValues == null) { | |
| propertyValues = new Map<string, unknown>(); | |
| weakMap.set(this, propertyValues); | |
| } | |
| let propName = `__memoized_value_${identifier}`; | |
| if (arguments.length > 0) | |
| propName += "_" + JSON.stringify(args); | |
| let returnedValue: any; | |
| if (propertyValues.has(propName)) | |
| returnedValue = propertyValues.get(propName); | |
| else { | |
| returnedValue = originalFunction.apply(this, args); | |
| propertyValues.set(propName, returnedValue); | |
| } | |
| return returnedValue; | |
| } | |
| return decorator; | |
| } | 
I am looking a method to find "any assigned decorator to properties within a class", for example:
class MyClass {
        @Memoize
        getNumber() {
            return Math.random();
        }
        @Memoize @rand
        get value() {
            return Math.random();
        }
    }
I want to get @memoize for getNumber:
let result = Reflect.GetDecorations(MyClass);
console.log(result);
// { value: ['Memoize' , 'rand'] , getNumber: ['Memoize'] }
Is there any possible solution?
You could use a WeakMap to avoid storing the magical property 😄
Thank you for this example!
I have created similar memoize decorator that can be expired after some time.
Just wondering, how is this different from a static property
@SteveStrong in certain cases where you want to use the prototype this is useful.
type hashCode = "hashCode";
/**
 * Indicates that this hashcode function should be memoized (calculated only once)
 * ***This should only be used on READONLY composite data types for predictable results***
 */
function memoized(target: ValueType, propertyKey: hashCode, descriptor: TypedPropertyDescriptor<() => number>) {
  const original = descriptor.get ? descriptor.get() : descriptor.value;
  Object.defineProperty(target.constructor.prototype, 'hashCode', {
    value: function hashCode() {
      if (!this.__hashCode__) {
        this.__hashCode__ = original.call(this);
      }
      return this.__hashCode__;
    }
  });
  descriptor.value = target.constructor.prototype.hashCode;
  return descriptor;
}
I found this to work - no WeakMap needed.
This might not work as well if you want to dynamically memoize things, but I am using it to memoize hashcode computations for immutable ValueTypes with Immutable.js
Edit: I am not sure how well this would work with sub-classes overriding a memoized function who also decorates the overridden function to memoize the calculation, it might loop infinitely?
Here is a WeakMap based version of this decorator, that respects single argument (by a reference for non-primitives)
@timjroberts Hi Tim, you are completely right and sorry about that! It did share the same value across multiple instances. I wish gist had given me a notification for your message, because I didn't noticed this mistake until I went to go use this in something and it broke the tests.
I went ahead and updated this so that problem is fixed. Additionally, I've updated the tests in this post to cover that situation.