GithubHelp home page GithubHelp logo

darrylhodgins / typescript-memoize Goto Github PK

View Code? Open in Web Editor NEW
129.0 4.0 18.0 542 KB

A memoize decorator for Typescript

Home Page: https://www.npmjs.com/package/typescript-memoize

TypeScript 91.30% JavaScript 8.70%
memoize subsequent-calls

typescript-memoize's Introduction

typescript-memoize

npm GitHub license Test

A memoize decorator for Typescript.

Installation

npm install --save typescript-memoize

Usage:

@Memoize(hashFunction?: (...args: any[]) => any)

You can use it in four ways:

  • Memoize a get accessor,
  • Memoize a method which takes no parameters,
  • Memoize a method which varies based on its first parameter only,
  • Memoize a method which varies based on some combination of parameters

You can call memoized methods within the same class, too. This could be useful if you want to memoize the return value for an entire data set, and also a filtered or mapped version of that same set.

Memoize a get accessor, or a method which takes no parameters

These both work the same way. Subsequent calls to a memoized method without parameters, or to a get accessor, always return the same value.

I generally consider it an anti-pattern for a call to a get accessor to trigger an expensive operation. Simply adding Memoize() to a get allows for seamless lazy-loading.

import {Memoize,MemoizeExpiring} from 'typescript-memoize';

class SimpleFoo {

    // Memoize a method without parameters
    @Memoize()
    public getAllTheData() {
       // do some expensive operation to get data
       return data;
    }

    // Memoize a method and expire the value after some time in milliseconds
    @MemoizeExpiring(5000)
    public getDataForSomeTime() {
        // do some expensive operation to get data
        return data;
    }

    // Memoize a getter
    @Memoize()
    public get someValue() {
        // do some expensive operation to calculate value
        return value;
    }

}

And then we call them from somewhere else in our code:

let simpleFoo = new SimpleFoo();

// Memoizes a calculated value and returns it:
let methodVal1 = simpleFoo.getAllTheData();

// Returns memoized value
let methodVal2 = simpleFoo.getAllTheData();

// Memoizes (lazy-loads) a calculated value and returns it:
let getterVal1 = simpleFoo.someValue;

// Returns memoized value
let getterVal2 = simpleFoo.someValue;

Memoize a method which varies based on its first parameter only

Subsequent calls to this style of memoized method will always return the same value.

I'm not really sure why anyone would use this approach to memoize a method with more than one parameter, but it's possible.

import {Memoize} from 'typescript-memoize';

class ComplicatedFoo {

	// Memoize a method without parameters (just like the first example)
	@Memoize()
	public getAllTheData() {
		// do some expensive operation to get data
		return data;
	}

	// Memoize a method with one parameter
	@Memoize()
	public getSomeOfTheData(id: number) {
		let allTheData = this.getAllTheData(); // if you want to!
		// do some expensive operation to get data
		return data;
	}

	// Memoize a method with multiple parameters
	// Only the first parameter will be used for memoization
	@Memoize()
	public getGreeting(name: string, planet: string) {
		return 'Hello, ' + name + '! Welcome to ' + planet;
	}

}

We call these methods from somewhere else in our code:

let complicatedFoo = new ComplicatedFoo();

// Returns calculated value and memoizes it:
let oneParam1 = complicatedFoo.getSomeOfTheData();

// Returns memoized value
let oneParam2 = complicatedFoo.getSomeOfTheData();

// Memoizes a calculated value and returns it:
// 'Hello, Darryl! Welcome to Earth'
let greeterVal1 = complicatedFoo.getGreeting('Darryl', 'Earth');

// Ignores the second parameter, and returns memoized value
// 'Hello, Darryl! Welcome to Earth'
let greeterVal2 = complicatedFoo.getGreeting('Darryl', 'Mars');

Memoize a method which varies based on some combination of parameters

Pass in a hashFunction which takes the same parameters as your target method, to memoize values based on all parameters, or some other custom logic

import {Memoize} from 'typescript-memoize';

class MoreComplicatedFoo {

	// Memoize a method with multiple parameters
	// Memoize will remember values based on keys like: 'name;planet'
	@Memoize((name: string, planet: string) => {
		return name + ';' + planet;
	})
	public getBetterGreeting(name: string, planet: string) {
		return 'Hello, ' + name + '! Welcome to ' + planet;
	}
	
	// Memoize based on some other logic
	@Memoize(() => {
		return new Date();
	})
	public memoryLeak(greeting: string) {
		return greeting + '!!!!!';
	}

	// Memoize also accepts parameters via a single object argument
	@Memoize({
		expiring: 10000, // milliseconds
		hashFunction: (name: string, planet: string) => {
			return name + ';' + planet;
		}
	})
	public getSameBetterGreeting(name: string, planet: string) {
		return 'Hello, ' + name + '! Welcome to ' + planet;
	}

}

We call these methods from somewhere else in our code. By now you should be getting the idea:

let moreComplicatedFoo = new MoreComplicatedFoo();

// 'Hello, Darryl! Welcome to Earth'
let greeterVal1 = moreComplicatedFoo.getBetterGreeting('Darryl', 'Earth');

// 'Hello, Darryl! Welcome to Mars'
let greeterVal2 = moreComplicatedFoo.getBetterGreeting('Darryl', 'Mars');

// Fill up the computer with useless greetings:
let greeting = moreComplicatedFoo.memoryLeak('Hello');

Memoize accepts one or more "tag" strings that allow the cache to be invalidated on command

Passing an array with one or more "tag" strings these will allow you to later clear the cache of results associated with methods or the getaccessors using the clear() function.

The clear() function also requires an array of "tag" strings.

import {Memoize} from 'typescript-memoize';

class ClearableFoo {

	// Memoize accepts tags
	@Memoize({ tags: ["foo", "bar"] })
	public getClearableGreeting(name: string, planet: string) {
		return 'Hello, ' + name + '! Welcome to ' + planet;
	}


	// Memoize accepts tags
	@Memoize({ tags: ["bar"] })
	public getClearableSum(a: number, b: number) {
		return a + b;
	}

}

We call these methods from somewhere else in our code.

import {clear} from 'typescript-memoize';

let clearableFoo = new ClearableFoo();

// 'Hello, Darryl! Welcome to Earth'
let greeterVal1 = clearableFoo.getClearableGreeting('Darryl', 'Earth');

// Ignores the second parameter, and returns memoized value
// 'Hello, Darryl! Welcome to Earth'
let greeterVal2 = clearableFoo.getClearableGreeting('Darryl', 'Mars');

// '3'
let sum1 = clearableFoo.getClearableSum(2, 1);

// Ignores the second parameter, and returns memoized value
// '3'
let sum2 = clearableFoo.getClearableSum(2, 2);

clear(["foo"]);

// The memoized values are cleared, return a new value
// 'Hello, Darryl! Welcome to Mars'
let greeterVal3 = clearableFoo.getClearableGreeting('Darryl', 'Mars');


// The memoized value is not associated with 'foo' tag, returns memoized value
// '3'
let sum3 = clearableFoo.getClearableSum(2, 2);

clear(["bar"]);

// The memoized values are cleared, return a new value
// 'Hello, Darryl! Welcome to Earth'
let greeterVal4 = clearableFoo.getClearableGreeting('Darryl', 'Earth');


// The memoized values are cleared, return a new value
// '4'
let sum4 = clearableFoo.getClearableSum(2, 2);

typescript-memoize's People

Contributors

darrylhodgins avatar dependabot[bot] avatar inventivetalentdev avatar jabiinfante avatar matissjanis avatar sandrolain avatar uber5001 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

typescript-memoize's Issues

Add a way to clear the cached values?

Hey!
Cool package, thanks for that!

I have a suggestion...

In a long running node process it might be useful to memoize things like DB/file access. I appreciate this might not be a popular/expected thing to do at all, but I like the neatness of caching these things with a decorator! (Currently just playing around with this package).

So, these things can obviously change so it'd be neat to be able to clear this cache. Maybe after an interval, though that doesn't have to be a part of the package as long as there's a way of clearing it (wouldn't mind specifying @Memoize(timeToLive:480) in the decorator though!)

I quickly whipped this up that'll blast all cached maps that does the trick for me right now. It might be nice to just clear the cache for a specific function, but because the maps don't store the function name that's a bit trickier right now! :-)

export function clear<T extends { [key: string]: any }>(obj: T) {
    const keys = Object.getOwnPropertyNames(obj);
    const stub = '__memoized_map_';

    for (const key of keys) {
        if (key !== undefined && key.startsWith(stub)) {
            delete obj[key];
        }
    }
}

Non-extensible object support

When object is not extensible this otherwise excellent decorator would not work.

This is not a corner case as all entities in frozen redux or @ngrx store are not extensible.

clearCacheTagsMap is always growing, memory leak?

Hi,
been using this to cache my datasource objects created by typeORM, we see a memory leak after adding the cache.
after profiling and debugging i saw that the clearCacheTagsMap in line 45 in the memoize-decorator.js is always growing, and it holds a reference to the cached items...

am i getting this wrong? is there a quick fix?
thanks got the help!

Proposal: Use LRUCache instead of Map

Using LRU cache https://www.npmjs.com/package/lru-cache would handle the duration and allow for more complex cache eviction policies.

Seems like a no brainer to me UNLESS you are trying to keep zero dependencies - which is a valid desire. In that case, I would recommend having dependency injection of a cache which would allow for this without complication.

Cache tags for generic classes?

First, kudos! I love this @memoize decorator implementation! My first exploration into implementing decorators was a form of memoization, but I did take it as far as you did.

However today I came across a use case for using @memoize that I'm not sure how to accomplish, and I'm wondering if it is even possible.

Scenario I

I have a generic class and need separate cached memoization for a method for each class that I use.

I was very pleasantly surprised this works as hoped!

Scenario II

I would like a distinct cache tag for each class I might use so I can clear the cache for a specific cached resource.

This does not seem possible since @memoize() takes literal cache keys and that doesn't help with my generic class.

So maybe, if feasible, since @memoize() can already differentiate between MyClass and MyClass it can automatically generate a distinct cache key for classes (including generic classes) to clear the cache separately?

Example:

import {memoize, clear} from 'typescript-memoize'

class MyClass<T extend A> {
  // memoize the findAll() method, 
  // would like a key for MyClass<T> but string literal won't work
  @memoize(tags: ['findAll'] ) 
  findAll() : T[] { /*implementation*/ }
}

class A {}
class B : extends A {}

b = new MyClass<B>()
allB = b.findAll()

// some time later...

// clear 'findAll' resources for a specific generic class instance
// but what key to use for the generic class?
clear(['findAll', 'MyClass<B>'] 

Not working with Webpack / Angular

Warning while compiling:

WARNING in ./node_modules/typescript-memoize/dist/memoize-decorator.js
3:24-31 Critical dependency: require function is used in a way in which dependencies cannot be statically extracted

Error in browser:

Error: Cannot find module "."
    at webpackMissingModule (memoize-decorator.js:3)

v1.1.1 is broken

The dist directory is not present in the packaged version of v1.1.1.

The library does not seem to import correctly from an ionic2 project.

I recently upgraded from version 0.0.4 of the library to 1.0.0-alpha.3, and since then the library does not seem to load correctly into my project.

To demonstrate the problem, I created an Ionic2 base application that artificially uses the memoize library: https://github.com/mohammadshamma/ionic2-base-and-typescript-memoize

If you attempt to run the repo above with the command ionic serve, you'd get a runtime error: Cannot find module ".".

cannot-find-module-dot

Improvement suggestion

Hi, I've seen you are setting a variable directly in the target object:

I can suggest 2 alternative to prevent having possible collisions with another namespace (its very unlikely but still possible)

A. use a Symbols this will completely prevent having name collisions on keys

B. use WeakMaps to store a result using a function as the key (that's probably the holy grail for your implementation). What I can imagine would be: a weak map to a regular map returning itself the cached value

{[callingMethod:Map<hash, any>]}

In this manner, there is no need to add an id to the function and if the function you Memoized does not exist anymore, there is no risk of a memory leak (maybe it's already the case, not sure… I haven't dug yet)

Compile to support ES5

Sorry, posted this without an actual description of the problem.

Could this library with UMD modules? I'm trying to load the decorator using systemjs but it's giving me a traceur error, because of the export function in dist/memoize-decorator.js.

In an ideal world browsers would natively support ES6 modules by now, but...

It should be as simple as setting module: UMD in your tsconfig.json and republishing the package.

Missing license information

On npmjs.com I see the license it set to MIT but the repository doesn't contain the license file.
Please provide license information and file in the project.

Thank you :)

How to clear cache of a method that has a custom `hashFunction` for a specific hash?

Example:

class MemoizedClass {
    @Memoize({
        tags: ["tag1"], hashFunction: function (arg1, arg2) {
            return arg1 + arg2
        }
    })
    public async expensiveFunction(arg1: string, arg2: string) {
        return arg1 + arg2
    }
}

async function main() {
    const instance = new MemoizedClass()

    const result = instance.expensiveFunction("1", "1")
    const result2 = instance.expensiveFunction("2", "2")

    clear(["tag1"]) // How to clear it only for arguments ("1", "1")?
}

main().catch(console.error);

@Memoize does not work if Class.toString Method has different signature

I have a special case, where I had to change the signature of a classes toString() method. Now the @Memoize decorator does not work anymore on any method in this class.

Argument of type 'Test' is not assignable to parameter of type 'Object'.
  Types of property 'toString' are incompatible.
    Type '(someParameter: string) => string' is not assignable to type '() => string'.ts(2345)

Sample Repo: https://codesandbox.io/s/typescript-memoize-tostring-5dy73h

Is there a way to work around this issue?

Memoize expiring

Due to the definitions, there is no option to set expiring to false, to provide infinite caching.

The memoize has this, and it would be usefull to have.

Thanks,
István

Not webpack friendly

I'm using https://github.com/storybooks/storybook along with Typescript 2.4.2 - storybook uses webpack to generate its output. When using typescript-memoize, I get the following:

WARNING in ./node_modules/typescript-memoize/dist/memoize-decorator.js
3:24-31 Critical dependency: require function is used in a way in which dependencies cannot be statically extracted

I think this is due to how the export is defined within a function.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.