As the author of [`moize`](https://github.com/planttheidea/moize), I created a consistently fast memoization library, but `moize` has a lot of features to satisfy a large number of edge cases. `micro-memoize` is a simpler approach, focusing on the core feature set with a much smaller footprint (~1.44kB minified+gzipped). Stripping out these edge cases also allows `micro-memoize` to be faster across the board than `moize`.
**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
### isEqual
`function(object1: any, object2: any): boolean`, _defaults to `isSameValueZero`_
Custom method to compare equality of keys, determining whether to pull from cache or not, by comparing each argument in order.
**NOTE**: The default method tests for [SameValueZero](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) equality, which is summarized as strictly equal while also considering `NaN` equal to `NaN`.
**NOTE**: If you don't want rejections to auto-remove the entry from cache, set `isPromise` to `false` (or simply do not set it), but be aware this will also remove the cache listeners that fire on successful resolution.
### maxSize
`number`, _defaults to `1`_
The number of values to store in cache, based on a [Least Recently Used](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29) basis. This operates the same as [`maxSize`](https://github.com/planttheidea/moize#maxsize) on `moize`, with the exception of the default being different.
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'], drops ['one', 'two'] from cache
```
**NOTE**: The default for `micro-memoize` differs from the default implementation of `moize`. `moize` will store an infinite number of results unless restricted, whereas `micro-memoize` will only store the most recent result. In this way, the default implementation of `micro-memoize` operates more like [`moize.simple`](https://github.com/planttheidea/moize#moizesimple).
### onCacheAdd
`function(cache: Cache, options: Options): void`
Callback method that executes whenever the cache is added to. This is mainly to allow for higher-order caching managers that use `micro-memoize` to perform superset functionality on the `cache` object.
console.log('memoized method has the following options applied: ', options);
},
});
memoized('foo', 'bar'); // cache has been added to
memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('bar', 'foo'); // cache has been added to
memoized('bar', 'foo');
memoized('bar', 'foo');
memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('foo', 'bar');
```
**NOTE**: This method is not executed when the `cache` is manually manipulated, only when changed via calling the memoized method.
### onCacheChange
`function(cache: Cache, options: Options): void`
Callback method that executes whenever the cache is added to or the order is updated. This is mainly to allow for higher-order caching managers that use `micro-memoize` to perform superset functionality on the `cache` object.
console.log('memoized method has the following options applied: ', options);
},
});
memoized('foo', 'bar'); // cache has changed
memoized('foo', 'bar');
memoized('foo', 'bar');
memoized('bar', 'foo'); // cache has changed
memoized('bar', 'foo');
memoized('bar', 'foo');
memoized('foo', 'bar'); // cache has changed
memoized('foo', 'bar');
memoized('foo', 'bar');
```
**NOTE**: This method is not executed when the `cache` is manually manipulated, only when changed via calling the memoized method. When the execution of other cache listeners (`onCacheAdd`, `onCacheHit`) is applicable, this method will execute after those methods.
### onCacheHit
`function(cache: Cache, options: Options): void`
Callback method that executes whenever the cache is hit, whether the order is updated or not. This is mainly to allow for higher-order caching managers that use `micro-memoize` to perform superset functionality on the `cache` object.
If your transformed keys require something other than `SameValueZero` equality, you can combine `transformKey` with [`isEqual`](#isequal) for completely custom key creation and comparison.
**NOTE**: `moize` offers a variety of convenience methods for this manual `cache` manipulation, and while `micro-memoize` allows all the same capabilities by exposing the `cache`, it does not provide any convenience methods.
#### memoized.cache.snapshot
`Object`
This is identical to the `cache` object referenced above, but it is a deep clone created at request, which will provide a persistent snapshot of the values at that time. This is useful when tracking the cache changes over time, as the `cache` object is mutated internally for performance reasons.
### memoized.fn
`function`
The original function passed to be memoized.
### memoized.isMemoized
`boolean`
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
`Object`
The [`options`](#options) passed when creating the memoized method.
## Benchmarks
All values provided are the number of operations per second (ops/sec) calculated by the [Benchmark suite](https://benchmarkjs.com/). 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 i7 8-core Arch Linux laptop with 16GB of memory using NodeJS version `10.15.0`. The default configuration of each library was tested with a fibonacci calculation based on the following parameters:
**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](https://github.com/lodash/lodash/issues/2115) is to just `JSON.stringify` the arguments, so that is what is used when needed.
### Single parameter (primitive only)
This is usually what benchmarks target for ... its the least-likely use-case, but the easiest to optimize, often at the expense of more common use-cases.
| | Operations / second |
| ----------------- | ------------------- |
| fast-memoize | 59,069,204 |
| **micro-memoize** | **48,267,295** |
| lru-memoize | 46,781,143 |
| Addy Osmani | 32,372,414 |
| lodash | 29,297,916 |
| ramda | 25,054,838 |
| mem | 24,848,072 |
| underscore | 24,847,818 |
| memoizee | 18,272,987 |
| memoizerific | 7,302,835 |
### Single parameter (complex object)
This is what most memoization libraries target as the primary use-case, as it removes the complexities of multiple arguments but allows for usage with one to many values.
| | Operations / second |
| ----------------- | ------------------- |
| **micro-memoize** | **40,360,621** |
| lodash | 30,862,028 |
| lru-memoize | 25,740,572 |
| memoizee | 12,058,375 |
| memoizerific | 6,854,855 |
| ramda | 2,287,030 |
| underscore | 2,270,574 |
| Addy Osmani | 2,076,031 |
| mem | 2,001,984 |
| fast-memoize | 1,591,019 |
### Multiple parameters (primitives only)
This is a very common use-case for function calls, but can be more difficult to optimize because you need to account for multiple possibilities ... did the number of arguments change, are there default arguments, etc.
| | Operations / second |
| ----------------- | ------------------- |
| **micro-memoize** | **33,546,353** |
| lru-memoize | 20,884,669 |
| memoizee | 7,831,161 |
| Addy Osmani | 6,447,448 |
| memoizerific | 5,587,779 |
| mem | 2,620,943 |
| underscore | 1,617,687 |
| ramda | 1,569,167 |
| lodash | 1,512,515 |
| fast-memoize | 1,376,665 |
### Multiple parameters (complex objects)
This is the most robust use-case, with the same complexities as multiple primitives but managing bulkier objects with additional edge scenarios (destructured with defaults, for example).
| | Operations / second |
| ----------------- | ------------------- |
| **micro-memoize** | **34,857,438** |
| lru-memoize | 20,838,330 |
| memoizee | 7,820,066 |
| memoizerific | 5,761,357 |
| mem | 1,184,550 |
| ramda | 1,034,937 |
| underscore | 1,021,480 |
| Addy Osmani | 1,014,642 |
| lodash | 1,014,060 |
| fast-memoize | 949,213 |
## Browser support
- Chrome (all versions)
- Firefox (all versions)
- Edge (all versions)
- Opera 15+
- IE 9+
- Safari 6+
- iOS 8+
- Android 4+
## Node support
- 4+
## Development
Standard stuff, clone the repo and `npm install` dependencies. The npm scripts available:
-`build` => run webpack to build development `dist` file with NODE_ENV=development
-`build:minifed` => run webpack to build production `dist` file with NODE_ENV=production
-`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
-`prepublish` => runs `compile-for-publish`
-`prepublish:compile` => run `lint`, `test`, `transpile:es`, `transpile:lib`, `dist`
-`test` => run AVA test functions with `NODE_ENV=test`
-`test:coverage` => run `test` but with `nyc` for coverage checker
-`test:watch` => run `test`, but with persistent watcher
-`transpile:lib` => run babel against all files in `src` to create files in `lib`
-`transpile:es` => run babel against all files in `src` to create files in `es`, preserving ES2015 modules (for [`pkg.module`](https://github.com/rollup/rollup/wiki/pkg.module))