パッケージの詳細

micro-memoize

planttheidea2.5mMIT5.1.0

A tiny, crazy fast memoization library for the 95% use-case

memoize, memoized, memoizer, memoization

readme

micro-memoize

A blazing fast memoization library that is tiny but feature-rich.

Table of contents

Importing

ESM:

import { memoize } from 'micro-memoize';

CommonJS:

const { memoize } = require('micro-memoize');

Usage

const toObject = (one: string, two: string) => ({ one, two });

const memoized = memoize(toObject);

console.log(memoized('one', 'two'));
console.log(memoized('one', 'two')); // pulled from cache

// Starting in `4.0.0`, you can compose memoized functions if you want to have multiple types of memoized
// versions based on different options.

const withUpToFive = memoize(memoized, { maxSize: 5 }); // { maxSize: 5 }
const withAsync = memoize(withUpToFive, { async: true }); // { async: true, maxSize: 5 }
const withCustomEquals = memoize(withAsync, { isEqual: deepEqual }); // { async: true, maxSize: 5, isEqual: deepEqual }

NOTE: The original function is the function used in the composition, the composition only applies to the options. In the example above, upToFive does not call simple, it calls fn.

Options

async

Identifies the value returned from the method as a Promise, which will result in one of two possible scenarios:

  • If the promise is resolved, it will fire the hit and update cache events.
  • If the promise is rejected, it will trigger auto-removal from cache as fire the delete cache event.
const fn = async (one: string, two: string) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error(JSON.stringify({ one, two })));
    }, 500);
  });
};

const memoized = memoize(fn, { isPromise: true });

memoized('one', 'two');

console.log(memoized.cache.snapshot.keys); // [['one', 'two']]
console.log(memoized.cache.snapshot.values); // [Promise]

setTimeout(() => {
  console.log(memoized.cache.snapshot.keys); // []
  console.log(memoized.cache.snapshot.values); // []
}, 1000);

NOTE: If you don't want rejections to auto-remove the entry from cache, set async to false (or simply do not set it), but be aware this will also remove the cache listeners that fire on successful resolution.

expires

The amount of time in milliseconds that you want a computed value to be stored in cache for this method.

const fn = (item: Record<string, any>) => item;

const MAX_AGE = 1000 * 60 * 5; // five minutes;

const expiringMemoized = memoize(fn, { maxAge: MAX_AGE });

You can also pass a custom configuration to handle conditional expiration.

const conditionalExpiringMemoized = memoize(fn, {
  expires: {
    after: MAX_AGE,
    shouldPersist: (item) => !item.expires,
    shouldRemove: (item) => item.updatedAt < new Date('2025-01-01').valueOf(),
    update: true,
  },
});

TIP: A common usage of this is in tandem with async for AJAX calls, and in that scenario the expected behavior is usually to have the expires countdown begin upon resolution of the promise. If this is your intended use case, you should also apply the update configuration option.

forceUpdate

Updates the cache forcibly for a given key when the predicate returns true. This is mainly useful if the function being memoized has time-based side-effects.

const fn = (item: string) => item;

let lastUpdate = Date.now();

const memoized = memoize(fn, {
  forceUpdate([item]: [string]) {
    const now = Date.now();
    const last = lastUpdated;

    lastUpdate = now;

    // its been more than 5 minutes since last update
    return last + 300000 < now;
  },
});

memoized('one');
memoized('one'); // pulled from cache

await Promise.resolve(() => setTimeout(resolve, MAX_AGE));

memoized('one'); // re-calls method and updates cache

isKeyEqual

Custom method to compare equality of keys, determining whether to pull from cache or not, by comparing the entire key.

type Arg = {
  one: string;
  two: null | string;
};

const fn = ({ one, two }: Arg) => [one, two];

const isFooEqualAndHasBar = (cacheKey: [Arg], key: [Arg]) =>
  cacheKey[0].one === key[0].one && cacheKey[1].hasOwnProperty('two') && key[1].hasOwnProperty('two');

const memoized = memoize(fn, { isKeyEqual: isFooEqualAndHasBar });

memoized({ one: 'two' }, { two: null });
memoized({ one: 'two' }, { two: 'three' }); // pulls from cache

isKeyItemEqual

_defaults to Object.is_

Custom method to compare equality of keys, determining whether to pull from cache or not, by comparing each argument in order. There are simple string options available for deep / shallow equality comparisons, or you can pass your own function for custom comparison.

type Arg = {
    one: {
        nested: string;
    };
    two: string;
};

const fn = ({ one, two }: Arg) => [one, two];

const deepMemoized = memoize(fn, { isKeyItemEqual: 'deep' });

deepMemoized({ one: { nested: 'one' }, two: 'two' });
deepMemoized({ one: { nested: 'one' }, two: 'two' }); // pulls from cache

const shallowMemoized = memoize(fn, { isKeyItemEqual: 'shallow' });

shallowMemoized({ one: 'one', two: 'two' });
shallowMemoized({ one: 'one', two: 'two' }); // pulls from cache

const customMemoized = memoize(fn, {
    isKeyItemEqual: (cacheKeyArg: Arg, keyArg: Arg) =>
        Object.keys(cacheKeyArg).length === 1 && Object.keys(keyArg).length === 1
    }
);

customMemoized({ one: 'two' };
customMemoized({ two: 'three' }); // pulls from cache

deep

Performs a deep equality comparison of each key item using the deepEqual method from fast-equals.

shallow

Performs a shallow equality comparison using the shallowEqual method from fast-equals.

maxArgs

The maximum number of arguments (starting from the first) used in creating the key for the cache.

const fn = (item1: string, item2: string, item3: string) => item1 + item2 + item3;

const memoized = memoize(fn, { maxArgs: 2 });

memoize('one', 'two', 'three');
memoize('one', 'two', 'four'); // pulls from cache, as the first two args are the same

If maxArgs is combined with either serialize or transformArgs, the following order is used:

  1. transform by transformArgs (if applicable)
  2. limit by maxArgs
  3. serialize by serializer (if applicable)

maxSize

defaults to 1

The number of values to store in cache, based on a Least Recently Used basis. This operates the same as maxSize on memoize.

const manyPossibleArgs = (one: string, two: string) => [one, two];

const memoized = memoize(manyPossibleArgs, { maxSize: 3 });

memoized.cache.on('delete', (event) => console.log('Deleted from cache: ', event.key));

console.log(memoized('one', 'two')); // ['one', 'two']
console.log(memoized('two', 'three')); // ['two', 'three']
console.log(memoized('three', 'four')); // ['three', 'four']

console.log(memoized('one', 'two')); // pulled from cache
console.log(memoized('two', 'three')); // pulled from cache
console.log(memoized('three', 'four')); // pulled from cache

console.log(memoized('four', 'five')); // ['four', 'five'], Deleted from cache: ['one', 'two']

serialize

Serializes the parameters passed into a string and uses this as the key for cache comparison. If a method is passed instead of a boolean, it is used as a custom serializer.

const fn = (mutableObject: { one: Record<string, any> }) => mutableObject.property;

const serializedMemoized = memoize(fn, { serialize: true });
const customSerializedMemoized = memoize(fn, {
  serialize: (args) => [JSON.stringify(args[0])],
});

If serialize is combined with either maxArgs or transformKey, the following order is used:

  1. transform by transformKey (if applicable)
  2. limit by maxArgs (if applicable)
  3. serialize

NOTE: This is much slower than the default key storage, and usually the same requirements can be meet with isKeyItemEqual: 'deep', so use at your discretion.

statsName

Name to use as unique identifier for the function when collecting statistics. Applying a statsName will also activate stats collection for that method.

startCollectingStats();

const fn = (item: string) => ({ item });

const memoized = memoize(fn, { statsName: 'my fancy identity' });

memoized('foo');
memoized('foo');
memoized('foo');

console.log(getStats('my fancy identity')); // { calls: 3, hits: 2, name: "my fancy identity", usage: "66.666666%" }

transformKey

Method that allows you transform the key that is used for caching, if you want to use something other than the arguments passed.

const ignoreFunctionArgs = (one: string, two: () => {}) => [one, two];

const memoized = memoize(ignoreFunctionArgs, {
  transformKey: (args) => [JSON.stringify(args[0])],
});

console.log(memoized('one', () => {})); // ['one', () => {}]
console.log(memoized('one', () => {})); // pulled from cache, ['one', () => {}]

If your transformed keys require something other than SameValueZero equality, you can combine transformKey with isKeyEqual for completely custom key creation and comparison.

const ignoreFunctionArg = (one: string, two: () => void) => [one, two];

const memoized = memoize(ignoreFunctionArg, {
  isMatchingKey: (key1, key2) => key1[0] === key2[0],
  // Cache based on the serialized first parameter
  transformKey: (args) => [JSON.stringify(args[0])],
});

console.log(memoized('one', () => {})); // ['one', () => {}]
console.log(memoized('one', () => {})); // pulled from cache, ['one', () => {}]

If transformKey is combined with either maxArgs or serialize, the following order is used:

  1. transform by transformKey
  2. limit by maxArgs (if applicable)
  3. serialize (if applicable)

Additional properties

memoized.cache

The cache object that is used internally. This is a highly-optimized structure, but has several methods for manual cache manipulation.Direct cache manipulation

The cache is an optimized linked list internally, so working with the cache directly is advised against. However, there are several exposed ways to introspect or manually manipulate the cache based on common use-cases.

memoized.cache.clear()

This will clear all values in the cache, resetting it to an empty state.

const memoized = memoize((item: string) => item);

memoized.cache.clear();

memoized.cache.delete(args)

This will remove the key based on the provided args from cache. args should be an Array of values, meant to reflect the arguments passed to the method.

const memoized = memoize((item: { one: string }) => item);

const arg = { one: 'one' };

memoized(arg);

memoized.cache.delete([arg]);

// will re-execute, as it is no longer in cache
memoized(arg);

NOTE: This will only remove keys that exist in the cache, and will do nothing if the key does not exist.

memoized.cache.get(args)

Returns the value in cache if the key based on args matches, else returns undefined. args should be an Array of values, meant to reflect the arguments passed to the method.

const memoized = memoize((one: string, two: string) => [one, two);

memoized('one', 'two');

console.log(memoized.cache.get(['one', 'two'])); // ["one","two"]
console.log(memoized.cache.get(['two', 'three'])); // undefined

memoized.cache.has(args)

This will return true if a cache entry exists for the key based on the args passed, else will return false. args should be an Array of values, meant to reflect the arguments passed to the method.

const memoized = memoize((one: string, two: string) => [one, two]);

memoized('one', 'two');

console.log(memoized.cache.has(['one', 'two'])); // true
console.log(memoized.cache.has(['two', 'three'])); // false

memoized.cache.on(name, listener)

Event listeners are available for different cache events, so that you can monitor cache changes over time.

add

Fires when an item has been added to cache. Receives the event:

interface OnAddEvent<Fn> {
  cache: Cache<Fn>;
  key: Key;
  reason: string | undefined;
  type: 'add';
  value: ReturnType<Fn>;
}

Example:

const fn = (one: string, two: string) => [one, two];

const memoized = memoize(fn, { maxSize: 2 });

memoized.cache.on('add', (event) => {
  console.log(`cache has been added to: ${JSON.stringify(event.key)}`);
});

memoized('foo', 'bar'); // cache has been added to: ["foo","bar"]
memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('bar', 'foo'); // cache has been added to: ["bar","foo"]
memoized('bar', 'foo');
memoized('bar', 'foo');
memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('foo', 'bar');
delete

Fires when an item has been removed from cache. Receives the event:

interface OnDeleteEvent<Fn> {
  cache: Cache<Fn>;
  key: Key;
  reason: string | undefined;
  type: 'delete';
  value: ReturnType<Fn>;
}

Example:

const fn = (one: string, two: string) => [one, two];

const memoized = memoize(fn);

memoized.cache.on('delete', (event) => {
  console.log(`cache entry was deleted (${event.reason}): ${JSON.stringify(event.key)}`);
});

memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('bar', 'foo'); // cache entry was deleted (evicted): ["foo","bar"]
memoized('bar', 'foo');
memoized('bar', 'foo');
memoized('foo', 'bar'); // cache entry was deleted (evicted): ["bar","foo"]
memoized('foo', 'bar');
memoized('foo', 'bar');
hit

Fires when an item has been found in cache. Receives the event:

interface OnHitEvent<Fn> {
  cache: Cache<Fn>;
  key: Key;
  reason: string | undefined;
  type: 'hit';
  value: ReturnType<Fn>;
}

Example:

const fn = (one: string, two: string) => [one, two];

const memoized = memoize(fn, { maxSize: 2 });

memoized.cache.on('hit', (event) => {
  console.log(`cache entry found: ${JSON.stringify(event.key)}`);
});

memoized('foo', 'bar');
memoized('foo', 'bar'); // cache entry was found: ["foo","bar"]
memoized('foo', 'bar'); // cache entry was found: ["foo","bar"]
memoized('bar', 'foo');
memoized('bar', 'foo'); // cache entry was found: ["bar","foo"]
memoized('bar', 'foo'); // cache entry was found: ["bar","foo"]
memoized('foo', 'bar'); // cache entry was found: ["foo","bar"]
memoized('foo', 'bar'); // cache entry was found: ["foo","bar"]
memoized('foo', 'bar'); // cache entry was found: ["foo","bar"]
update

Fires when cache was reordered based on finding an older entry in cache and making it the most recent. Receives the event:

interface OnUpdateEvent<Fn> {
  cache: Cache<Fn>;
  key: Key;
  reason: string | undefined;
  type: 'update';
  value: ReturnType<Fn>;
}

Example:

const fn = (one: string, two: string) => [one, two];

const memoized = memoize(fn, { maxSize: 2 });

memoized.cache.on('update', (event) => {
  console.log(`cache has updated: ${JSON.stringify(event.key)}`);
});

memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('bar', 'foo');
memoized('foo', 'bar'); // cache has updated: ["foo","bar"]
memoized('bar', 'foo'); // cache has updated: ["bar","foo"]
memoized('foo', 'bar'); // cache has updated: ["foo","bar"]
memoized('foo', 'bar');

memoized.cache.set(args, value)

This will manually add the value at the key based on args in cache if the key does not already exist; if the key exists, it will update the value. args should be an Array of values, meant to reflect the arguments passed to the method.

// single parameter is straightforward
const memoized = memoize((item: string) => item: string);

memoized.add(['one'], 'two');

// pulls from cache
memoized('one');

memoized.cache.snapshot

The cache is mutated internally for performance reasons, so logging out the cache at a specific step in the workflow may not give you the information you need. As such, to help with debugging you can request the cache.snapshot, which provides a well-formed snapshot of the cache:

type CacheSnapshot = {
  entries: Array<[Key, ReturnType<Fn>]>;
  keys: Key[];
  size: number;
  values: Array<ReturnType<Fn>>;
};

memoized.fn

The original function passed to be memoized.

memoized.isMemoized

Hard-coded to true when the function is memoized. This is useful for introspection, to identify if a method has been memoized or not.

memoized.options

The options passed when creating the memoized method.

Statistics

As-of version 5, you can collect statistics of memoize to determine if your cached methods are effective. To activate stats collection for a given memoized method, you must provide a statsName.

import { getStats, memoize, startCollectingStats } from 'memoize';

startCollectingStats();

const fn = (one: string, two: string) => [one, two];

const foo = memoize((one: string, two: string) => [one, two], {
  statsName: 'foo',
});
const bar = memoize((one: string, two: string) => `${one} ${two}`, {
  statsName: 'bar',
});
// this will have no stats collected
const baz = memoize((one: string, two: string) => ({ one, two }));

foo('one', 'two');
bar('one', 'two');
foo('one', 'two');
baz('one', 'two');

console.log(getStats('foo'));
/*
{
  "calls": 2,
  "hits": 1,
  "name": "foo",
  "usage": "50.000000%"
}
*/
console.log(getStats());
/*
{
  "calls": 3,
  "hits": 1,
  "profiles: {
    foo: {
      "calls": 2,
      "hits": 1,
      "name": "foo",
      "usage": "50.000000%"
    },
    "bar": {
      "calls": 1,
      "hits": 0,
      "name: "bar",
      "usage": "0.000000%"
    }
  },
  "usage": "33.333333%"
}
*/

NOTE: It is recommended not to activate this in production, as it has a small (but unnecessary) performance impact.

clearStats()

Cear statistics on memoized functions.

clearStats(); // clears all stats
clearStats('stats-name'); // clears stats only for 'stats-name'

getStats(statsName)

Get the statistics for a specific function, or globally.

startCollectingStats();

const fn = (one: string, two: string) => [one, two];

const memoized = memoize(fn);

const otherFn = (one: string[]) => one.slice(0, 1);

const otherMemoized = memoize(otherFn, { statsName: 'otherMemoized' });

memoized('one', 'two');
memoized('one', 'two');
otherMemoized(['three']);

getStats('otherMemoized');
/*
{
  "calls": 1,
  "hits": 0,
  "name": "otherMemoized",
  "usage": "0.000000%"
}
*/
getStats();
/*
{
  "calls": 3,
  "hits": 1,
  "profiles": {
    "otherMemoized": {
      "calls": 1,
      "hits": 0,
      "name": "otherMemoized",
      "usage": "0.000000%"
    }
  },
  "usage": "33.3333%"
}
*/

isCollectingStats()

Are statistics being collected on memoization usage.

startCollectingStats();
isCollectingStats(); // true
stopCollectingStats();
isCollectingStats(); // false

startCollectingStats()

Start collecting statistics on memoized functions with defined statsName options.

startCollectingStats();
s;

stopCollectingStats()

Stop collecting statistics on memoized functions with defined statsName options.

stopCollectingStats();

Benchmarks

All values provided are the number of operations per second (ops/sec) calculated by the Benchmark suite. Note that underscore, lodash, and ramda do not support mulitple-parameter memoization (which is where micro-memoize really shines), so they are not included in those benchmarks.

Benchmarks was performed on an i9 16-core Linux laptop with 64GB of memory using NodeJS version 24.11.0. The default configuration of each library was tested with a fibonacci calculation based on the following parameters:

  • Single primitive = 35
  • Single array = [35]
  • Single object = { number: 35 }
  • Multiple primitives = 35, true
  • Multiple arrays = [35], [true]
  • Multiple objects = { number: 35 }, { isComplete: true }

NOTE: Not all libraries tested support multiple parameters out of the box, but support the ability to pass a custom resolver. Because these often need to resolve to a string value, a common suggestion is to just JSON.stringify the arguments, so that is what is used when needed.

Single primitive parameter

┌───────────────┬─────────────────┐
│ Name          │ Ops / sec       │
├───────────────┼─────────────────┤
│ micro-memoize │ 19223652.896122 │
├───────────────┼─────────────────┤
│ lru-memoize   │ 18832637.003931 │
├───────────────┼─────────────────┤
│ fast-memoize  │ 18656534.751361 │
├───────────────┼─────────────────┤
│ mem           │ 18002171.563837 │
├───────────────┼─────────────────┤
│ lodash        │ 16593919.372016 │
├───────────────┼─────────────────┤
│ ramda         │ 15005783.713612 │
├───────────────┼─────────────────┤
│ addy osmani   │ 14595725.136778 │
├───────────────┼─────────────────┤
│ underscore    │ 14394123.608684 │
├───────────────┼─────────────────┤
│ memoizee      │ 14143422.526051 │
├───────────────┼─────────────────┤
│ memoizerific  │ 9098305.800331  │
└───────────────┴─────────────────┘
Fastest was "micro-memoize".

Single array parameter

┌───────────────┬─────────────────┐
│ Name          │ Ops / sec       │
├───────────────┼─────────────────┤
│ micro-memoize │ 17778297.542508 │
├───────────────┼─────────────────┤
│ lodash        │ 16538329.518316 │
├───────────────┼─────────────────┤
│ lru-memoize   │ 15486317.927651 │
├───────────────┼─────────────────┤
│ memoizee      │ 12094740.371154 │
├───────────────┼─────────────────┤
│ memoizerific  │ 8539559.392909  │
├───────────────┼─────────────────┤
│ mem           │ 5084143.134978  │
├───────────────┼─────────────────┤
│ ramda         │ 4779283.1256    │
├───────────────┼─────────────────┤
│ underscore    │ 4536871.295139  │
├───────────────┼─────────────────┤
│ addy osmani   │ 3930939.284558  │
├───────────────┼─────────────────┤
│ fast-memoize  │ 2280509.909947  │
└───────────────┴─────────────────┘
Fastest was "micro-memoize".

Single object parameter

┌───────────────┬─────────────────┐
│ Name          │ Ops / sec       │
├───────────────┼─────────────────┤
│ micro-memoize │ 17660028.460248 │
├───────────────┼─────────────────┤
│ lodash        │ 16156245.220626 │
├───────────────┼─────────────────┤
│ lru-memoize   │ 15870878.729231 │
├───────────────┼─────────────────┤
│ memoizee      │ 12282231.140112 │
├───────────────┼─────────────────┤
│ memoizerific  │ 8673522.519546  │
├───────────────┼─────────────────┤
│ mem           │ 3990242.123108  │
├───────────────┼─────────────────┤
│ ramda         │ 3740951.604329  │
├───────────────┼─────────────────┤
│ underscore    │ 3610838.023906  │
├───────────────┼─────────────────┤
│ addy osmani   │ 3091088.143196  │
├───────────────┼─────────────────┤
│ fast-memoize  │ 2037255.509585  │
└───────────────┴─────────────────┘
Fastest was "micro-memoize".

Multiple primitive parameters

┌───────────────┬─────────────────┐
│ Name          │ Ops / sec       │
├───────────────┼─────────────────┤
│ micro-memoize │ 15890715.277889 │
├───────────────┼─────────────────┤
│ lru-memoize   │ 14795822.850331 │
├───────────────┼─────────────────┤
│ memoizee      │ 8878241.434757  │
├───────────────┼─────────────────┤
│ memoizerific  │ 6746446.702835  │
├───────────────┼─────────────────┤
│ addy osmani   │ 5088726.104365  │
├───────────────┼─────────────────┤
│ mem           │ 4919428.914271  │
├───────────────┼─────────────────┤
│ ramda         │ 3931584.638274  │
├───────────────┼─────────────────┤
│ underscore    │ 3787236.410261  │
├───────────────┼─────────────────┤
│ lodash        │ 3521399.9522    │
├───────────────┼─────────────────┤
│ fast-memoize  │ 1921167.148268  │
└───────────────┴─────────────────┘
Fastest was "micro-memoize".

Multiple array parameters

┌───────────────┬─────────────────┐
│ Name          │ Ops / sec       │
├───────────────┼─────────────────┤
│ micro-memoize │ 16477581.429235 │
├───────────────┼─────────────────┤
│ lru-memoize   │ 15973386.171668 │
├───────────────┼─────────────────┤
│ memoizee      │ 8916765.084082  │
├───────────────┼─────────────────┤
│ memoizerific  │ 7394462.762389  │
├───────────────┼─────────────────┤
│ mem           │ 4230286.398649  │
├───────────────┼─────────────────┤
│ ramda         │ 3424812.638986  │
├───────────────┼─────────────────┤
│ underscore    │ 3271019.504488  │
├───────────────┼─────────────────┤
│ lodash        │ 3058024.57579   │
├───────────────┼─────────────────┤
│ addy osmani   │ 2556872.10781   │
├───────────────┼─────────────────┤
│ fast-memoize  │ 1664942.513511  │
└───────────────┴─────────────────┘
Fastest was "micro-memoize".

Multiple object parameters

┌───────────────┬─────────────────┐
│ Name          │ Ops / sec       │
├───────────────┼─────────────────┤
│ micro-memoize │ 16816183.60597  │
├───────────────┼─────────────────┤
│ lru-memoize   │ 15847710.978627 │
├───────────────┼─────────────────┤
│ memoizee      │ 8963861.306564  │
├───────────────┼─────────────────┤
│ memoizerific  │ 7279084.235795  │
├───────────────┼─────────────────┤
│ mem           │ 2891756.11455   │
├───────────────┼─────────────────┤
│ ramda         │ 2562787.384413  │
├───────────────┼─────────────────┤
│ underscore    │ 2508806.688495  │
├───────────────┼─────────────────┤
│ lodash        │ 2413141.440839  │
├───────────────┼─────────────────┤
│ addy osmani   │ 1882996.512818  │
├───────────────┼─────────────────┤
│ fast-memoize  │ 1431840.065495  │
└───────────────┴─────────────────┘
Fastest was "micro-memoize".

Support

  • Chrome 47+
  • Firefox 43+
  • Edge 14s+
  • Opera 34+
  • Safari 10+
  • Node 6+

Development

Standard stuff, clone the repo and npm install dependencies. The npm scripts available:

  • benchmark => run benchmarks against well-known alternative packages
  • build => run rollup to build the dist files
  • build:cjs => run rollup to build the dist files specific to CJS requires
  • build:esm => run rollup to build the dist files specific to ESM imports
  • build:min => run rollup to build the dist files specific to pre-minified files
  • build:umd => run rollup to build the dist files specific to legacy environments, such as Node 10
  • clean: remove dist folder
  • clean:cjs: remove dist/cjs folder
  • clean:esm: remove dist/esm folder
  • clean:min: remove dist/min folder
  • clean:umd: remove dist/umd folder
  • dev => run webpack dev server to run example app (playground!)
  • dist => runs build and build-minified
  • lint => run ESLint against all files in the src folder
  • release => runs the release process, which publishes the latest version of the package
  • release:beta => runs the beta release process, which publishes the next beta version of the package
  • release:scripts => run the precursor tasks as part of the release process
  • test => run unit tests
  • test:coverage => run test but with coverage
  • test:watch => run test but with persistent watcher
  • typecheck => run tsc to verify types are valid

更新履歴

micro-memoize CHANGELOG

5.1.0

Enhancements

  • #132 - Update expiration on async methods when update: true is set in the expires config
  • #131 - Faster internal checks if a node has been removed

Fixes

  • #133 - fix listeners being skipped when one is removed mid-iteration

5.0.1

  • #127 - fix type narrowing not being respected on memoized functions (fixes #126)
  • #128 - bolster documentation related to 'deep' and 'shallow' options for isKeyItemIEqual (fixes #125)
  • #129 - fix legacy types being out of date

5.0.0

This was a rewrite from the ground up, and this increased speed while also allowing for easier augmentation of feature sets. The feature set augmentation was made so easy, that I was able to deprecate the separate moize library and absorb those features into micro-memoize while still keeping bundle size very small (~2.62KB gzipped).

Enhancements

  • Added a bunch of new options:
    • expires (auto-expiration of cache entries, with customizable handling of expirations)
    • forceUpdate (allow forcible updates when called for methods with side-effects)
    • maxArgs (limit the args used for cache key purposes)
    • serialize (serialize the args for cache key purposes)
    • statsName (statistics collection)
  • Added support for manual cache manipulation via ergonomic methods
  • Replaced clunky onCache* listeners with more ergonomic cache.on() event listeners, which can handle any number of listeners
  • Linked list storage for faster deletes and updates in caches larger than 1

Breaking changes

  • Renamed several existing options for clarity
    • isPromise => async
    • isMatchingKey => isKeyEqual
    • isEqual => isKeyItemEqual
  • Removed cache callback handlers:
    • onCacheAdd (replaced with fn.cache.on('add', handler))
    • onCacheHit (replaced with fn.cache.on('hit', handler))
    • onCacheUpdate (replaced with fn.cache.on('delete', handler) for deletes, fn.cache.on('update', handler) for updates)
  • Package is now ESM primary (but CommonJS should still be supported)
  • Dedicated import for ESM has changed (dist/esm/index.mjs => dist/es/index.mjs)
  • Browser / Node support has updated (requires ES2016+)
  • Removed min build

4.2.0

  • #122 - Add CJS import for CommonJS requires

4.1.3

  • #121 - Avoid reference to broken source maps (fix for #79)

4.1.2

4.1.1 (Bad version - do not use)

  • #102 - avoid publishing development-only files for less node_modules bloat

4.1.0

Enhancements

  • Types now have direct exports instead of requiring the MicroMemoize namespace. That namespace has been labeled as deprecated, and will be removed in the next major version change in favor of the direct type exports.

Bugfixes

  • #97 - src files included in publish, and referenced from *.d.ts files
  • mjs/*d.ts files renamed to mjs/*.d.mts to align with NodeJS standard
  • #101 - fixed benchmark using mem incorrectly for complex object parameters or multiple parameter calls

4.0.15

  • #99 - mjs import does not have typings surfaced

4.0.14

  • Republish of #87

4.0.13 (Bad version - do not use)

  • #87 - Default generic values for exposed types, to avoid unintentional breaking changes from #85

4.0.12

  • #84 - Fix inferred typing of memoized function
  • #85 - Follow-up on #84, further improving inferred typing via pass-throughs.

4.0.11

  • Fix #79 - Sourcemaps referencing incorrect hierarchy

4.0.10

  • Fix #76 - noUncheckedIndexedAccess support on TS 4.1+

4.0.9

  • Update npm search keywords and documentation

4.0.8

  • Improve typings to support strict mode in TypeScript

4.0.7

  • Create more targeted getKeyIndex helpers for more speed in each key situation

4.0.6

  • Use standard then interface (.then(onFulfilled, onRejected)) instead of ES spec (.then(onFulfilled).catch(onRejected)) for broader support

4.0.5

  • Fix failure when getKeyIndex is used and no keys are in the cache

4.0.4

  • Use .pop() to cap cache to maxSize when possible (slight performance improvement)

4.0.3

  • Namespace types under MicroMemoize namespace (which is how it was for 4.0.0, but it got lost)

4.0.2

  • Make Cache class consumable in types

4.0.1

  • Fix types for consumption

4.0.0

Enhancements

  • You can now compose memoized functions with their options (see Composition)
  • Update to use Cache class instead of plain object (~10% performance improvement)

Breaking changes

  • memoized.cacheSnapshot has been deprecated in favor of memoized.cache.snapshot
  • Memoizing an already-memoized function no longer returns the function passed (now composes, see Composition)

3.0.2

  • Fix types declarations to ensure signature of fn passed is retained
  • Throw an error when the first parameter passed is not a function

3.0.1

  • Fix types declaration for Options to allow custom keys / indices

3.0.0

  • Rewrite in TypeScript
  • Use rollup for builds of all packages

Breaking changes

  • CommonJS requires no longer require .default
  • Types contract is much stricter
  • Utility methods are no longer deep-linkable
    • Not technically exposed in the API, but was relied upon by other libraries)

2.1.2

  • Fix issue where isMatchingKey was not being used with promise updater
  • Remove requirement of Object.assign existing globally
  • Add common use-case static handlers for up to 3 arguments, falling back to pre-existing dynamic handlers for more (faster comparison / argument cloning)

2.1.1

  • Upgrade to babel 7
  • Add "sideEffects": false for better tree-shaking with webpack

2.1.0

2.0.4

  • Fix issue with recursive calls to memoized function created discrepancy between keys and values in cache

2.0.3

  • More TypeScript typings (thanks again @rtorr)

2.0.2

  • Fix TypeScript typings (thanks @rtorr)

2.0.1

  • Fix TypeScript typings (thanks @Crecket)

2.0.0

  • Add isMatchingKey method to provide match test on entire key vs iterative equality

Enhancements

  • isMatchingKey will matching on entire key vs isEqual, which does an iterative comparison of arguments in order
  • Add size property to cache

Breaking changes

  • The return value from transformKey must be an Array (would previously coalesce it for you)

1.8.1

  • Fix getKeyIndex being passed as memoize for promises

1.8.0

  • Include the memoized function itself as the third parameter to onCacheAdd, onCacheChange, and onCacheHit firings

1.7.0

  • Fire onCacheHit and onCacheChange when promise functions successfully resolve

1.6.3

  • Replace native slice usages with cloneArray utility

1.6.2

  • Convert dist files to be built using rollup instead of webpack

1.6.1

  • Optimize slice calls for key storage (performance)

1.6.0

  • Add onCacheAdd option
  • Pass through unused properties in options for higher-order memoization library usage

1.5.0

1.4.0

  • Add options as second parameter to onCacheChanged

1.3.2

  • Make additional properties (cache, cacheSnapshot, isMemoized, options) configurable for higher-order memoization library usage

1.3.1

  • Only reorder keys when matching cache entry is not first key

1.3.0

1.2.0

  • Add isPromise option
  • Add typings for Flowtype and TypeScript

1.1.0

1.0.1

  • Delay argument-to-key generation until stored as new cache value (speed improvement of ~35%)

1.0.0

  • Initial release