1.0.14 • Published 5 years ago

auto-memoize v1.0.14

Weekly downloads
2
License
MIT
Repository
github
Last release
5 years ago

Fast javascript memoize function, with optimized memoize strategies provided. Class and methods memoize decorators.

Build Status Coverage Status npm version

npm i -S auto-memoize
import { memoize, CreateOnce, CallOnce } from 'auto-memoize'
import { fibo } from './fibonaci'

// memoize function
let memoizeFibonaci = memoize(fibo)
memoizeFibonaci(5)
memoizeFibonaci(5) //cache hit

// Create one instance with same parameters
@CreateOnce
class Person {
  firstName
  lastName
  constructor(firstName, lastName) {
    console.log(`Creating ${firstName} ${lastName}`)
    this.firstName = firstName,
    this.lastName = lastName
  }
  // call method once with same parameters
  @CallOnce
  getGreeting(greet: string) {
    console.log(greet)
    const name = `${this.firstName} ${this.lastName}`
    return { name, greeting }
  }
}
const person1 = new Person('Boris', 'Johnson') 
// logged 'Creating Boris Johnson'
const person2 = new Person('Boris', 'Johnson')
person1 == person2 // true

const g1 = person1.getGreeting('hi') // logged 'hi'
const g2 = person1.getGreeting('hi')
g1 == g2 // true

memoize

Default implementation

Caches by all parameters as a key, comparing them with Object.is algorithm

const calc = require('expensive-calculation')
const { memoize } = require('auto-memoize')

const memoCalc = memoize(calc)

const param = {a: 'one'}
memoCalc(param, 1)
memoCalc(param, 2)
memoCalc(param, 1) // cache hit
memoCalc({a: 'one'}, 1) // no cache hit, because reference is different

WeakMap implementation

Caches by first parameter as a key and using ES6 WeakMap as a cache. Garbage collector automatically removes entries from cache, when no references for keys are present.

const calc = require('expensive-calculation')
const { memoize } = require('auto-memoize')

const memoCalc = memoize(calc, 'weak')

const param = {a: 'one'}
memoCalc(param, 1)
memoCalc(param, 2) // cache hit
memoCalc({a: 'one'}, 1) // no cache hit, because reference is different

Deep comparison implementation

Caches by all parameters as a key and compares them by content, if references are different. fast-deep-equal npm package does the comparison. It is performing better on big objects, than JSON.stringify.

const calc = require('expensive-calculation')
const { memoize } = require('auto-memoize')

const memoCalc = memoize(calc, 'deep')

const param = {a: 'one'}
memoCalc(param)
memoCalc(param) // cache hit
memoCalc({a: 'one'}) // cache hit

String key implementation

Caches all parameters as a string key. For cache is used ES6 Map. JSON.stringify is used to turn every parameter to string. It is useful for function with several parameters, but not big objects, that takes time to turn into string.

const calc = require('expensive-calculation')
const { memoize } = require('auto-memoize')

const memoCalc = memoize(calc, 'string')

const param = {a: 'one'}
memoCalc(param, 1)
memoCalc(param, 1) // cache hit
memoCalc({a: 'one'}, 2) 
memoCalc({a: 'one'}, 1) // cache hit

Custom key implementation

Caches by key from function, that returns string. For cache is used ES6 Map

const calc = require('expensive-calculation')
const { memoize } = require('memoize.js')

const memoCalc = memoize(calc, (p) => p.a)

const param = {a: 'one'}
memoCalc(param)
memoCalc(param) // cache hit
memoCalc({a: 'one'}) // cache hit

Benchmarking strategies

Small object parameter

const simple = {
  data: {
    p1: {
      name: 'auto-memoize'
      }
    }
  }
memoized(simple, 1, 1)
Strategyops/sec
1callback4,765,946
2weak3,751,169
3deep1,211,864
4default1,004,562
5string341,206

Object parameter

const data = { ...3 package.json }
memoized(data, 1, 1)
Strategyops/sec
1callback5,131,611
2weak2,663,508
3default992,947
4deep832,758
5string33,763

Different reference object parameter

const data = { ...3 package.json }
memoized(Object.assign({}, data), 1, 1)
Strategyops/sec
1callback2,195,159
2deep708,301
3string33,098
weak833,162n/a
default1,484n/a

Primitive parameters

memoized("argument", 1, true)
Strategyops/sec
1callback3,340,699
2deep1,320,397
3default1,330,912
4string548,254
weakn/a

Typings includes types for:

  • memoized function parameters
  • memoized function return type

getCache

Utility function to get cache instance from memoized function, class or method. It will return instance of CacheMap. It enables you to retrieve cache records and invalidate cache.

CacheMap

For different cache strategies different keys are applicable. Key types:

  • default - any[]
  • weak - object
  • string - string (parameters joined by -)
  • deep - any[]
  • custom key - string
export interface CacheMap<K = any, V = any> {
    get(key: K): V | undefined
    has(key: K): boolean
    set(key: K, result: V): this
    clear(): void
}

Examples

import { getCache, CreateOnce, CallOnce, memoize } from 'auto-memoize'
import { fibo } from './fibonaci'

const memo = memoize(fibo)
memo(5)

// cache from meoized function
getCache(memo).has([5]) // true
getCache(memo).clear()
getCache(memo).has([5]) // false

@CreateOnce
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  @CallOnce
  greet(greeting) {
    const { firstName } = this
    return { greeting, firstName }
  }
}
const person = new Person('A', 'B')
person.greet('Hello')

// cache from  decorated 'CreateOnce' or 'CreateOnceBy' class
getCache(Person)

// cache from  decorated 'CallOnce' or 'CallOnceBy' class
getCache(person.greet)

Decorators

Docs

@CreateOnce

Class Decorator used to create same instance of class when same parameters are passed. It follows functional design pattern memoize, also Flyweight design pattern, when one instance of Class is created once with exact state. It defines how objects with state can be shared. If class constructor has no parameters, class decorated with CreateOnce becomes singleton. If this class is extended, effect of decorator will be applied on child classes. Parent constructor will always be constructor that creates class instance. Even if child constructor accepts different parameters. If child class is decorated, child class will have cache effect. It will cache instances by default 'auto-memoize' strategy. Decorator @CreateOnceBy can be configured with other cache hit strategy

@CreateOnceBy

Class Decorator used to created same instance of class when same parameters are passed. It follows functional design pattern memoize, also Flyweight design pattern, when one instance of Class is created once with exact state. It defines how objects with state can be shared. It will cache instances by strategy from decorator parameter. Decorator @CreateOnce is preconfigured with default cache hit strategy. If this class is extended, effect of decorator will be applied on child classes. Parent constructor will always be constructor that creates class instance. Even if child constructor accepts different parameters. If child class is decorated, child class will have cache effect.

@CallOnce

Class method decorator used to create memoized class method. Method that will return same output with same parameters passed. It follows memoize functional design pattern. It will cache by default auto-memoize strategy. Decorator @CallOnceBy can be configured with other cache hit strategy.

@CallOnceBy

Class method decorator used to create 'memoized' class method. Method that will return same output with same parameters passed. It follows memoize functional design pattern. It must be configured with cache hit strategy. Decorator @CallOnce is already preconfgured with default strategy.

Examples

Caching with default strategy

import { CreateOnce, CallOnce } from 'auto-memoize';

// Creates one class instance per constructor parameter 
@CreateOnce
class Greeter {
  greeting
    constructor(greeting) {
      this.greeting = greeting
    }

    // Calls method once per parameter unique
    // method with cache
    @CallOnce
    greet(name) {
      return { greet: `${this.greeting}, ${name}!` }
    }
}

const greeter1 = new Greeter('Hello')
const greeter2 = new Greeter('Hello')
greeter1 == greeter2 // true

const greet1 = greeter1.greet('Boris')
const greet2 = greeter1.greet('Boris')
const greet3 = greeter2.greet('Boris')
greet1 == greet2 // true
greet1 == greet3 // true

Caching with provided strategy

import { CreateOnceBy, CallOnceBy } from 'auto-memoize';
 
// Creates one class instance per constructor parameter 
@CreateOnceBy('string')
class CustomGreeter {
  greeting
    constructor(greeting) {
      this.greeting = greeting
    }

    // Calls method once per parameter unique
    // method with cache
    @CallOnceBy(person => person.name)
    greet(person) {
      return { greet: `${this.greeting}, ${person.name}!` }
    }
}
  const greeter1 = new Greeter('Hello')
  const greeter2 = new Greeter('Hello')
  greeter1 == greeter2 // true
  
  const greet1 = greeter1.greet({name: 'Boris'})
  const greet2 = greeter1.greet({name: 'Boris'})
  const greet3 = greeter2.greet({name: 'Boris'})
  greet1 == greet2 // true
  greet1 == greet3 // true

Class Singleton

import { CreateOnce } from 'auto-memoize';

// Always will return same instance of class
@CreateOnce
class Greeter {
  constructor() {
    }
    greet(person) {
      return { greet: `Hello, ${person.name}!` }
    }
}
const greeter1 = new Greeter()
const greeter2 = new Greeter()
greeter1 == greeter2 // true
1.0.14

5 years ago

1.0.13

5 years ago

1.0.12

5 years ago

1.0.11

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago