├── .gitignore
├── .project
├── LICENSE
├── README.md
├── benchmark
├── addy-osmani.js
├── index.js
├── micro-memoize.min.js
├── micro-memoize.min.js.br
├── micro-memoize.min.js.gz
└── real-world-simulation.js
├── dist
├── nano-memoize.js
└── nano-memoize.js.map
├── index.d.ts
├── index.js
├── package-lock.json
├── package.json
├── src
└── index.js
└── test
├── index.html
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | .parcel-cache
61 |
62 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | nano-memoize
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2023 Simon Y. Blackwell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.codacy.com/app/syblackwell/nano-memoize?utm_source=github.com&utm_medium=referral&utm_content=anywhichway/nano-memoize&utm_campaign=Badge_Grade) [](https://www.npmjs.org/package/nano-memoize) [](https://www.npmjs.org/package/nano-memoize) [](https://bundlephobia.com/package/nano-memoize) [](https://packagephobia.com/result?p=nano-memoize)
2 | # Faster than fast, smaller than micro ... nano-memoizer.
3 |
4 | # Introduction
5 |
6 | Version 3.x.x of nano-memoize was modified to use newer versions of JavaScript built-in classes and take advantage of current v8 loop optimizations. As a result, the minified/brotli size of 3.0.4 at 487 bytes is 30% smaller and is slightly faster that v2.x.x and v1.x.x.
7 |
8 | The `nano-memoize` library although very small and very fast, is no longer the smallest or fastest JavaScript memoizer. Although it lacks some configuration options, the [memize](https://github.com/aduth/memize) library is the smallest and fastest JavaScript memoizer. In fact, `memize` has been around for some time, I just did not know about it.
9 |
10 | # Usage
11 |
12 | `npm install nano-memoize`
13 |
14 | # API
15 |
16 | The API is a subset of the `moize` API.
17 |
18 | ```javascript
19 | const memoized = nanomemoize(sum(a,b) => a + b);
20 | memoized(1,2); // 3
21 | memoized(1,2); // pulled from cache
22 | ```
23 |
24 | `nanomemoize(function,options) returns function`
25 |
26 | The shape of options is:
27 |
28 | ```javascript
29 | {
30 | // only use the provided maxArgs for cache look-up, useful for ignoring final callback arguments
31 | maxArgs: number,
32 | // call last argument of memoized multi-args functions after a number of milliseconds via a timeout after the
33 | // cached result has been returned, perhaps to ensure that callbacks are invoked, does not cache the timemout result
34 | // e.g. nanomemoize(function(a,b,callback) { var result = a + b; callback(result); return result; },{maxArgs:2,callTimeout:0});
35 | callTimeout: number,
36 | // number of milliseconds to cache a result, set to `Infinity` or `-1` to never create timers or expire
37 | maxAge: number,
38 | // the serializer/key generator to use for single argument functions (optional, not recommended)
39 | // must be able to serialize objects and functions, by default a Map is used internally without serializing
40 | serializer: function,
41 | // the equals function to use for multi-argument functions (optional, try to avoid) e.g. deepEquals for objects
42 | equals: function,
43 | // forces the use of multi-argument paradigm, auto set if function has a spread argument or uses `arguments` in its body.
44 | vargs: boolean
45 | }
46 | ```
47 |
48 | The returned function will also have these methods:
49 |
50 | `.clear()` clears the cache for the function.
51 |
52 | `.keys()` returns an array of arrays with each array being the arguments provided on a call of the function.
53 |
54 | `.values()` returns an array of values with each array being the results of a function call with the same index position as the keys.
55 |
56 | # Benchmarks
57 |
58 | Tests no-longer show `nano-memoize` is the smallest and fastest JavaScript memoizer for single and multiple argument functions accepting primitives and objects.
59 |
60 | Although it lacks some configuration options, the [memize](https://github.com/aduth/memize) library is the smallest and fastest JavaScript memoizer.
61 |
62 | Although `fast-memoize` used to be a contender and is hugely popular, its performance has dropped substantially.
63 |
64 | `lodash` is excluded from multiple argument tests because it only memoizes the first argument.
65 |
66 | Benchmarks can vary dramatically from O/S to O/S or Node version to Node version or even execution run to execution run. These tests were run on a Windows 10 Pro 64bit 2.8ghz i7 machine with 16GB RAM and Node v18.13.0. Garbage collection was forced between each sample run to minimize its impact on results.
67 |
68 | Starting cycles for functions with a single primitive parameter...
69 |
70 | | Name | Ops / sec | Relative margin of error | Sample size |
71 | |---------------|------- |-------------------------|-------------|
72 | | nano-memoize | 177,773,071 | ± 1.17% | 87 |
73 | | fast-memoize | 148,920,057 | ± 1.37% | 88 |
74 | | micro-memoize | 148,405,982 | ± 1.46% | 90 |
75 | | memize | 135,435,506 | ± 2.60% | 82 |
76 | | iMemoized | 117,844,380 | ± 1.65% | 88 |
77 | | moize | 96,796,008 | ± 1.80% | 85 |
78 | | underscore | 54,815,804 | ± 1.28% | 87 |
79 | | lodash | 54,617,110 | ± 1.30% | 87 |
80 | | lru-memoize | 41,426,130 | ± 1.16% | 87 |
81 | | memoizee | 31,747,590 | ± 1.52% | 85 |
82 | | addy-osmani | 14,848,551 | ± 1.76% | 82 |
83 | | memoizerific | 12,835,863 | ± 1.84% | 85 |
84 |
85 |
86 | Starting cycles for functions with a single object parameter...
87 |
88 | | Name | Ops / sec | Relative margin of error | Sample size |
89 | | ------- |------- |------------- |------- |
90 | | nano-memoize | 105,666,095 | ± 1.84% | 84 |
91 | | memize | 100,137,512 | ± 3.52% | 78 |
92 | | micro-memoize | 81,031,228 | ± 1.85% | 82 |
93 | | moize | 80,331,910 | ± 1.88% | 84 |
94 | | lodash | 57,681,325 | ± 3.58% | 77 |
95 | | iMemoized | 31,021,746 | ± 2.16% | 82 |
96 | | lru-memoize | 27,346,729 | ± 3.08% | 73 |
97 | | memoizee | 26,171,811 | ± 2.85% | 79 |
98 | | memoizerific | 12,116,210 | ± 2.31% | 82 |
99 | | underscore | 11,796,099 | ± 2.54% | 81 |
100 | | addy-osmani | 1,333,797 | ± 1.93% | 82 |
101 | | fast-memoize | 1,046,331 | ± 2.41% | 81 |
102 |
103 |
104 | Starting cycles for functions with multiple parameters that contain only primitives...
105 |
106 | | Name | Ops / sec | Relative margin of error | Sample size |
107 | | ------- |------- |------------- |------- |
108 | | memize | 92,754,248 | ± 2.04% | 80 |
109 | | nano-memoize | 73,566,138 | ± 2.45% | 83 |
110 | | moize | 62,359,029 | ± 3.22% | 77 |
111 | | micro-memoize | 60,110,589 | ± 2.94% | 77 |
112 | | lru-memoize | 31,263,360 | ± 2.08% | 84 |
113 | | memoizee | 17,110,335 | ± 3.01% | 76 |
114 | | iMemoized | 10,532,281 | ± 2.83% | 79 |
115 | | memoizerific | 8,042,920 | ± 3.18% | 77 |
116 | | addy-osmani | 3,942,524 | ± 3.38% | 79 |
117 | | fast-memoize | 1,076,636 | ± 3.09% | 80 |
118 |
119 |
120 | Starting cycles for functions with multiple parameters that contain objects...
121 |
122 | | Name | Ops / sec | Relative margin of error | Sample size |
123 | | ------- |------- |------------- |------- |
124 | | memize | 67,190,875 | ± 3.73% | 70 |
125 | | nano-memoize | 49,960,425 | ± 2.25% | 81 |
126 | | micro-memoize | 47,635,797 | ± 3.70% | 73 |
127 | | moize | 44,910,757 | ± 4.52% | 68 |
128 | | lru-memoize | 27,472,119 | ± 2.76% | 77 |
129 | | memoizee | 17,352,541 | ± 2.79% | 75 |
130 | | underscore | 10,748,866 | ± 3.62% | 74 |
131 | | iMemoized | 10,544,215 | ± 2.77% | 77 |
132 | | memoizerific | 8,346,696 | ± 3.66% | 74 |
133 | | addy-osmani | 954,701 | ± 2.18% | 82 |
134 | | fast-memoize | 652,447 | ± 3.60% | 74 |
135 |
136 | Starting cycles for alternative cache types...
137 |
138 | | Name | Ops / sec | Relative margin of error | Sample size |
139 | | ------- |------- |------------- |------- |
140 | | nanomemoize deep equals (hash-it isEqual) | 107,990,728 | ± 2.59% | 83 |
141 | | nanomemoize deep equals (lodash isEqual) | 96,543,576 | ± 2.20% | 84 |
142 | | moize deep equals (lodash isEqual) | 88,305,997 | ± 2.55% | 82 |
143 | | micro-memoize deep equals (fast-equals) | 86,511,616 | ± 1.67% | 85 |
144 | | moize deep equals (fast-deep-equal) | 85,948,355 | ± 1.55% | 79 |
145 | | micro-memoize deep equals (lodash isEqual) | 85,231,542 | ± 1.83% | 84 |
146 | | moize deep equals (fast-equals) | 84,844,833 | ± 1.77% | 85 |
147 | | moize deep equals (hash-it isEqual) | 76,605,158 | ± 1.82% | 83 |
148 | | micro-memoize deep equals (hash-it isEqual) | 73,619,713 | ± 2.26% | 82 |
149 | | nanomemoize fast equals (fast-equals deep) | 64,710,177 | ± 1.59% | 79 |
150 | | micro-memoize deep equals (fast-deep-equal) | 62,658,012 | ± 2.95% | 78 |
151 | | nanomemoize fast equals (fast-deep-equals) | 42,443,623 | ± 1.91% | 82 |
152 |
153 |
154 | Most libraries test repeated calls using the same arguments that guarantee memoized cache hits. In the real world this is unlikely. For a real world simulation where only 20% of the function calls are memoized and they take and return mixed argument types, nanomemoize is by far fastest.
155 |
156 | Note, the reasons for the slow performance of `memize` below are unknown. It is possible that the library is not being used correctly. Also, it is known that the library is optimized for repeated calls with the same arguments in a series rather than sporadically. If your usage pattern is more similar to the simulation, you should test `memize` further.
157 |
158 | Starting real world simulation...
159 |
160 | | Name | Ops / sec | Relative margin of error | Sample size |
161 | | ------- |------- |------------- |------- |
162 | | nanomemoizedFunctions | 2,172,477 | ± 2.63% | 81 |
163 | | moizeMemoizedFunctions | 1,279,591 | ± 2.30% | 83 |
164 | | microMemoizedFunctions | 1,206,613 | ± 2.62% | 81 |
165 | | fastMemoizedFunctions | 441,783 | ± 2.94% | 79 |
166 | | memizeMemoizedFunctions | 3,727 | ± 34.45% | 9 |
167 |
168 |
169 | If you want similar performance for intersection, union or Cartesian product also see:
170 |
171 | - https://github.com/anywhichway/intersector
172 | - https://github.com/anywhichway/unionizor
173 | - https://github.com/anywhichway/cxproduct
174 |
175 | For a complete high performance solution to Cartesian product and set operations for Arrays and Sets with a standardized API, plus the addition of the standard map/reduce/find operations to Set see:
176 |
177 | - https://github.com/anywhichway/array-set-ops
178 |
179 |
180 | # Release History (reverse chronological order)
181 |
182 | 2023-11-16 v3.0.16 Fixed issue [63](https://github.com/anywhichway/nano-memoize/issues/63). Thanks @darkship.
183 |
184 | 2023-11-16 v3.0.15 Enhanced TypeScript types. Thanks @darkship.
185 |
186 | 2023-09-29 v3.0.14 Added Typescript typings to `package.json` (thanks @gnarea) and some badges (thanks @silverwind).
187 |
188 | 2023-06-17 v3.0.13 Corrected version below which was set to v4.0.0. Minor optimizations. Restructured README to put usage above benchmarks.
189 |
190 | 2023-06-16 v3.0.12 Bumping version for updates of dependencies. Updated all memoization libraries to latest versions. Also added `memize` to tests and updated to latest LTS version of Node.js. The Node update had a material impact on performance of several memoziation libraries.
191 |
192 | 2023-04-19 v3.0.11 Bumping version for dependabot and other third party pull requests impacting package.json.
193 |
194 | 2023-04-08 v3.0.10 Enhanced real world simulation.
195 |
196 | 2023-04-07 v3.0.9 Added real world simulation. Removed .parcel-cache from deployment.
197 |
198 | 2023-02-22 v3.0.8 Documentation updates.
199 |
200 | 2023-02-15 v3.0.7 Documentation updates.
201 |
202 | 2023-02-15 v3.0.6 Documentation updates.
203 |
204 | 2023-02-15 v3.0.5 Documentation updates.
205 |
206 | 2023-02-04 v3.0.4 A code walkthrough revealed an opportunity to remove unused code from v2.x.x.
207 |
208 | 2023-02-02 v3.0.3 Added unit test for `maxAge`. Adjusted varArg unit tests for more accuracy. Slight optimizations to multi argument memoized functions. Slight improvement to cache clearing that may reduce GC. Updated license file for copyright period. Updated docs on `callTimeout` for clarity.
209 |
210 | 2023-02-01 v3.0.2 Fixed https://github.com/anywhichway/nano-memoize/issues/52 with custom equals functions not consistently working. `fast-equals` or `lodash.isEqual` now work. Slight performance degradation, but still generally the fastest.
211 |
212 | 2022-01-29 v3.0.1 Fixed build issue where root index.js was not getting updated.
213 |
214 | 2023-01-28 v3.0.0 Slight size optimization. 25% speed improvement. Moved to module format. There is a known issue with providing `fast-equals` or `lodash.isEqual` as an optional comparison function. Unit tests pass, but the functions fail under load. The `hash-it` object equivalence function does work. A formerly undocumented method `.keyValues()` has been deprecated since it is no longer relevant with the new optimizations.
215 |
216 | 2022-12-08 v2.0.0 Removed callTimeout from TypeScript typings since it was not implemented and there are no plans to implement. Bumped version to 2.0.0 since this may break some users.
217 |
218 | 2022-10-20 v1.3.1 Bumping version for https://github.com/anywhichway/nano-memoize/pull/41
219 |
220 | 2022-03-30 v1.3.0 Dropped support for `dist` and `browser` directories.
221 |
222 | 2022-03-15 v1.2.2 Bumped minor version for TS typings and some package dependency updates.
223 |
224 | 2020-11-02 v1.2.1 Added main: "index.js" reference in package.json.
225 |
226 | 2020-06-18 v1.2.0 Enhanced multi-arg handling so that arg lengths must match precisely (thanks @amazingmarvin), unless `maxArgs` is passed as an option.
227 | Also added `callTimeout` to go ahead and call underlying function to ensure callbacks are invoked if necessary.
228 |
229 | 2020-05-26 v1.1.11 [Fixed Issue 17]Fixed https://github.com/anywhichway/nano-memoize/issues/17. It is not known when this bug made its way into the code.
230 |
231 | 2020-02-30 v1.1.10 Moved growl to dev dependency.
232 |
233 | 2020-01-30 v1.1.9 Code style improvements.
234 |
235 | 2019-11-29 v1.1.8 Corrected typos in documentation.
236 |
237 | 2019-09-25 v1.1.7 Manually created an IE 11 compatible version by removing arrow functions and replacing Object.assign
238 | with a custom function. Minor size increase from 660 to 780 Brotli bytes. Also eliminated complex `bind` approach in
239 | favor of closures. The latest v8 engine handles closures as fast as bound arguments for our case and we are not as
240 | concerned with older browser performace (particularly given how fast the code is anyway). Converted for loops to run in
241 | reverse, which for our case is faster (but is not always guranteed to be faster).
242 |
243 | 2019-09-17 v1.1.6 Added a manually transpiled es5_ie11.html file with an Object.assign polyfill to the test directory to verify
244 | compatibility with IE11. Modified unit tests so they are ES5 compatible. All tests pass. Added `sideEffects=false` to package.json.
245 |
246 | 2019-06-28 v1.1.5 Improved documentation. Updated version of `micro-memoize` used for benchmark testing. No code changes.
247 |
248 | 2019-05-31 v1.1.4 [Fixed Issue 7](https://github.com/anywhichway/nano-memoize/issues/7).
249 |
250 | 2019-04-09 v1.1.3 [Fixed Issue 6](https://github.com/anywhichway/nano-memoize/issues/6). Minor speed and size improvements.
251 |
252 | 2019-04-02 v1.1.2 Speed improvements for multiple arguments. Now consistently faster than `fast-memoize` and `nano-memoize` across multiple test runs. Benchmarks run in a new test environment. The benchmarks for v1.1.1 although correct from a relative perspective, grossly understated actual performance due to a corrupt testing environment.
253 |
254 | 2019-03-25 v1.1.1 Pushed incorrect version with v1.1.0. This corrects the version push.
255 |
256 | 2019-03-25 v1.1.0 Added use of `WeakMap` for high-speed caching of single argument functions when passed objects. The `serializer` option no longer defaults to `(value) => JSON.stringify(value)` so if you want to treat objects that have the same string representation as the same, you will have to provide a `serializer`.
257 |
258 | 2019-03-24 v1.0.8 Updated/corrected documentation.
259 |
260 | 2019-03-24 v1.0.7 Made smaller and faster. Renamed `sngl` to `sng` and `mltpl` to `mlt`. Converted all functions to arrow functions except `sng` and `mlt`. Simplified and optimized `mlt`. Removed `()` around args to single argument arrow function definitions, e.g. `(a) => ...` became `a => ...`. Replaced the arg signature `()` in arrow functions with `_`, which is shorter. Eliminated multiple character variables except for those used in options to request memoization. Collapsed `setTimeout` into a single location. Defined `const I = Infinity`. Eliminated `()` around `? :` condition expressions. Changed `{}` to `Object.create(null)`. Documented all variables. Moved some variables around for clarity. Moved `options` into a destructing argument.
261 |
262 | 2019-03-20 v1.0.6 Updated documentation.
263 |
264 | 2019-03-11 v1.0.5 Now supports setting `maxAge` to Infinity and no timers will be created to expire caches.
265 |
266 | 2019-02-26 v1.0.4 Further optimized cache expiration. See [Issue 4](https://github.com/anywhichway/nano-memoize/issues/4)
267 |
268 | 2019-02-16 v1.0.3 Fixed README formatting
269 |
270 | 2019-02-16 v1.0.2 Further optimizations to deal with [Issue 4](https://github.com/anywhichway/nano-memoize/issues/4). `expireInterval` introduced in v1.0.1 removed since it is no longer needed. Also, 25% reduction in size. Code no longer thrashes when memoizing a large number of functions.
271 |
272 | 2019-02-16 v1.0.1 Memo expiration optimization. Special appreciation to @titoBouzout and @popbee who spent a good bit of time reviewing code for optimization and making recommendations. See [Issue 4](https://github.com/anywhichway/nano-memoize/issues/4) for the conversation.
273 |
274 | 2018-04-13 v1.0.0 Code style improvements.
275 |
276 | 2018-02-07 v0.1.2 Documentation updates
277 |
278 | 2018-02-07 v0.1.1 Documentationand benchmark updates
279 |
280 | 2018-02-01 v0.1.0 Documentation updates. 50 byte decrease.
281 |
282 | 2018-01-27 v0.0.7b BETA Documentation updates.
283 |
284 | 2018-01-27 v0.0.6b BETA Minor size and speed improvements.
285 |
286 | 2018-01-27 v0.0.5b BETA Fixed edge case where multi-arg key may be shorter than current args.
287 |
288 | 2018-01-27 v0.0.4b BETA Fixed benchmarks. Removed maxSize. More unit tests. Fixed maxAge.
289 |
290 | 2018-01-27 v0.0.3b BETA More unit tests. Documentation. Benchmark code in repository not yet running.
291 |
292 | 2018-01-24 v0.0.2a ALPHA Minor speed enhancements. Benchmark code in repository not yet running.
293 |
294 | 2018=01-24 v0.0.1a ALPHA First public release. Benchmark code in repository not yet running.
295 |
--------------------------------------------------------------------------------
/benchmark/addy-osmani.js:
--------------------------------------------------------------------------------
1 | /*
2 | * memoize.js
3 | * by @philogb and @addyosmani
4 | * with further optimizations by @mathias
5 | * and @DmitryBaranovsk
6 | * perf tests: http://bit.ly/q3zpG3
7 | * Released under an MIT license.
8 | */
9 | module.exports = function memoize( fn ) {
10 | return function () {
11 | var args = Array.prototype.slice.call(arguments),
12 | hash = "",
13 | i = args.length;
14 | currentArg = null;
15 | while (i--) {
16 | currentArg = args[i];
17 | hash += (currentArg === Object(currentArg)) ?
18 | JSON.stringify(currentArg) : currentArg;
19 | fn.memoize || (fn.memoize = {});
20 | }
21 | return (hash in fn.memoize) ? fn.memoize[hash] :
22 | fn.memoize[hash] = fn.apply(this, args);
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/benchmark/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*MIT License
4 | Core benchmark code copied from micro-memoize
5 |
6 | Copyright (c) 2018 Tony Quetano
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | const vm = require("node:vm");
28 | const v8 = require("v8");
29 |
30 | v8.setFlagsFromString('--expose_gc');
31 | const gc = vm.runInNewContext('gc');
32 |
33 | const Benchmark = require('benchmark');
34 | const Table = require('cli-table');
35 | const ora = require('ora');
36 |
37 | const underscore = require('underscore').memoize;
38 | const lodash = require('lodash').memoize;
39 | const ramda = require('ramda').memoize;
40 | const memoizee = require('memoizee');
41 | const fastMemoize = require('fast-memoize');
42 | const addyOsmani = require('./addy-osmani');
43 | const memoizerific = require('memoizerific');
44 | const lruMemoize = require('lru-memoize').default;
45 | const microMemoize = require('micro-memoize');
46 | const iMemoized = require('iMemoized');
47 | const nanomemoize = require('../dist/nano-memoize.js').default;
48 | const moize = require('moize');
49 | const memize = require('memize');
50 |
51 |
52 | const deepEquals = require('lodash').isEqual;
53 | const fastEquals = require('fast-equals').deepEqual;
54 | const fastDeepEqual = require('fast-deep-equal/ES6');
55 |
56 | const showResults = (benchmarkResults) => {
57 | const table = new Table({
58 | head: ['Name', 'Ops / sec', 'Relative margin of error', 'Sample size']
59 | });
60 |
61 | benchmarkResults.forEach((result) => {
62 | const name = result.target.name;
63 | const opsPerSecond = result.target.hz.toLocaleString('en-US', {
64 | maximumFractionDigits: 0
65 | });
66 | const relativeMarginOferror = `± ${result.target.stats.rme.toFixed(2)}%`;
67 | const sampleSize = result.target.stats.sample.length;
68 |
69 | table.push([name, opsPerSecond, relativeMarginOferror, sampleSize]);
70 | });
71 |
72 | console.log(table.toString()); // eslint-disable-line no-console
73 | };
74 |
75 | const sortDescResults = (benchmarkResults) => {
76 | return benchmarkResults.sort((a, b) => {
77 | return a.target.hz < b.target.hz ? 1 : -1;
78 | });
79 | };
80 |
81 | const spinner = ora('Running benchmark');
82 |
83 | let results = [];
84 |
85 | const onCycle = (event) => {
86 | results.push(event);
87 | ora(event.target.name).succeed();
88 | gc();
89 | };
90 |
91 | const onComplete = () => {
92 | spinner.stop();
93 |
94 | const orderedBenchmarkResults = sortDescResults(results);
95 |
96 | showResults(orderedBenchmarkResults);
97 | };
98 |
99 | const fibonacci = (number) => {
100 | return number < 2 ? number : fibonacci(number - 1) + fibonacci(number - 2);
101 | };
102 |
103 | const fibonacciMultiplePrimitive = (number, isComplete) => {
104 | if (isComplete) {
105 | return number;
106 | }
107 |
108 | const firstValue = number - 1;
109 | const secondValue = number - 2;
110 |
111 | return (
112 | fibonacciMultiplePrimitive(firstValue, firstValue < 2) + fibonacciMultiplePrimitive(secondValue, secondValue < 2)
113 | );
114 | };
115 |
116 | const fibonacciSingleArray = (array) => {
117 | return array[0] < 2
118 | ? array[0]
119 | : fibonacciSingleArray([array[0] - 1]) +
120 | fibonacciSingleArray([array[0] - 2]);
121 | };
122 |
123 | const fibonacciMultipleArray = (array, check) => {
124 | if (check[0]) {
125 | return array[0];
126 | }
127 |
128 | const firstValue = array[0] - 1;
129 | const secondValue = array[0] - 2;
130 |
131 | return (
132 | fibonacciMultipleArray([firstValue], [firstValue < 2]) +
133 | fibonacciMultipleArray([secondValue], [secondValue < 2])
134 | );
135 | };
136 |
137 | const fibonacciMultipleMixed = (number, check) => {
138 | if (check.isComplete) {
139 | return number;
140 | }
141 |
142 | const firstValue = number - 1;
143 | const secondValue = number - 2;
144 |
145 | return (
146 | fibonacciMultipleMixed(firstValue, {
147 | isComplete: firstValue < 2
148 | }) +
149 | fibonacciMultipleMixed(secondValue, {
150 | isComplete: secondValue < 2
151 | })
152 | );
153 | };
154 |
155 | const fibonacciSingleObject = ({number}) => {
156 | return number < 2
157 | ? number
158 | : fibonacciSingleObject({number: number - 1}) + fibonacciSingleObject({number: number - 2});
159 | };
160 |
161 | const fibonacciMultipleObject = ({number}, check) => {
162 | if (check.isComplete) {
163 | return number;
164 | }
165 |
166 | const firstValue = number - 1;
167 | const secondValue = number - 2;
168 |
169 | return (
170 | fibonacciMultipleObject({number:firstValue}, {
171 | isComplete: firstValue < 2
172 | }) +
173 | fibonacciMultipleObject({number:secondValue}, {
174 | isComplete: secondValue < 2
175 | })
176 | );
177 | };
178 |
179 | const runSingleParameterSuite = () => {
180 | const fibonacciSuite = new Benchmark.Suite('Single primitive parameter');
181 | const fibonacciNumber = 35;
182 |
183 | const mUnderscore = underscore(fibonacci);
184 | const mLodash = lodash(fibonacci);
185 | // const mRamda = ramda(fibonacci);
186 | const mMemoizee = memoizee(fibonacci);
187 | const mFastMemoize = fastMemoize(fibonacci);
188 | const mAddyOsmani = addyOsmani(fibonacci);
189 | const mMemoizerific = memoizerific(Infinity)(fibonacci);
190 | const mLruMemoize = lruMemoize(Infinity)(fibonacci);
191 | const mMoize = moize(fibonacci);
192 | const mMicroMemoize = microMemoize(fibonacci);
193 | const mIMemoized = iMemoized.memoize(fibonacci);
194 | const mNano = nanomemoize(fibonacci);
195 | const mMemize = memize(fibonacci);
196 |
197 |
198 | return new Promise((resolve) => {
199 | fibonacciSuite
200 | .add('nano-memoize', () => {
201 | mNano(fibonacciNumber);
202 | })
203 | .add('addy-osmani', () => {
204 | mAddyOsmani(fibonacciNumber);
205 | })
206 | .add('lodash', () => {
207 | mLodash(fibonacciNumber);
208 | })
209 | .add('lru-memoize', () => {
210 | mLruMemoize(fibonacciNumber);
211 | })
212 | .add('memoizee', () => {
213 | mMemoizee(fibonacciNumber);
214 | })
215 | .add('memoizerific', () => {
216 | mMemoizerific(fibonacciNumber);
217 | })
218 | /*.add('ramda', () => {
219 | mRamda(fibonacciNumber);
220 | })*/
221 | .add('underscore', () => {
222 | mUnderscore(fibonacciNumber);
223 | })
224 | .add('iMemoized', () => {
225 | mIMemoized(fibonacciNumber);
226 | })
227 | .add('micro-memoize', () => {
228 | mMicroMemoize(fibonacciNumber);
229 | })
230 | .add('moize', () => {
231 | mMoize(fibonacciNumber);
232 | })
233 | .add('fast-memoize', () => {
234 | mFastMemoize(fibonacciNumber);
235 | })
236 | .add('memize', () => {
237 | mMemize(fibonacciNumber);
238 | })
239 | .on('start', () => {
240 | console.log(''); // eslint-disable-line no-console
241 | console.log('Starting cycles for functions with a single primitive parameter...'); // eslint-disable-line no-console
242 |
243 | results = [];
244 |
245 | spinner.start();
246 | })
247 | .on('cycle', onCycle)
248 | .on('complete', () => {
249 | onComplete();
250 | resolve();
251 | })
252 | .run({
253 | async: true
254 | });
255 | });
256 | };
257 |
258 | const runSingleParameterObjectSuite = () => {
259 | const fibonacciSuite = new Benchmark.Suite('Single object parameter');
260 | const fibonacciNumber = {number:35};
261 |
262 | const mUnderscore = underscore(fibonacciSingleObject);
263 | const mLodash = lodash(fibonacciSingleObject);
264 | // const mRamda = ramda(fibonacciSingleObject);
265 | const mMemoizee = memoizee(fibonacciSingleObject);
266 | const mFastMemoize = fastMemoize(fibonacciSingleObject);
267 | const mAddyOsmani = addyOsmani(fibonacciSingleObject);
268 | const mMemoizerific = memoizerific(Infinity)(fibonacciSingleObject);
269 | const mLruMemoize = lruMemoize(Infinity)(fibonacciSingleObject);
270 | const mMoize = moize(fibonacciSingleObject);
271 | const mMicroMemoize = microMemoize(fibonacciSingleObject);
272 | const mIMemoized = iMemoized.memoize(fibonacciSingleObject);
273 | const mNano = nanomemoize(fibonacciSingleObject);
274 | const mMemize = memize(fibonacciSingleObject);
275 |
276 |
277 | return new Promise((resolve) => {
278 | fibonacciSuite
279 | .add('nano-memoize', () => {
280 | mNano(fibonacciNumber);
281 | })
282 | .add('addy-osmani', () => {
283 | mAddyOsmani(fibonacciNumber);
284 | })
285 | .add('lodash', () => {
286 | mLodash(fibonacciNumber);
287 | })
288 | .add('lru-memoize', () => {
289 | mLruMemoize(fibonacciNumber);
290 | })
291 | .add('memoizee', () => {
292 | mMemoizee(fibonacciNumber);
293 | })
294 | .add('memoizerific', () => {
295 | mMemoizerific(fibonacciNumber);
296 | })
297 | /*.add('ramda', () => {
298 | mRamda(fibonacciNumber);
299 | })*/
300 | .add('underscore', () => {
301 | mUnderscore(fibonacciNumber);
302 | })
303 | .add('iMemoized', () => {
304 | mIMemoized(fibonacciNumber);
305 | })
306 | .add('micro-memoize', () => {
307 | mMicroMemoize(fibonacciNumber);
308 | })
309 | .add('moize', () => {
310 | mMoize(fibonacciNumber);
311 | })
312 | .add('fast-memoize', () => {
313 | mFastMemoize(fibonacciNumber);
314 | })
315 | .add('memize', () => {
316 | mMemize(fibonacciNumber);
317 | })
318 | .on('start', () => {
319 | console.log(''); // eslint-disable-line no-console
320 | console.log('Starting cycles for functions with a single object parameter...'); // eslint-disable-line no-console
321 |
322 | results = [];
323 |
324 | spinner.start();
325 | })
326 | .on('cycle', onCycle)
327 | .on('complete', () => {
328 | onComplete();
329 | resolve();
330 | })
331 | .run({
332 | async: true
333 | });
334 | });
335 | };
336 |
337 | const runMultiplePrimitiveSuite = () => {
338 | const fibonacciSuite = new Benchmark.Suite('Multiple parameters (Primitive)');
339 | const fibonacciNumber = 35;
340 | const isComplete = false;
341 |
342 | const mMemoizee = memoizee(fibonacciMultiplePrimitive);
343 | const mFastMemoize = fastMemoize(fibonacciMultiplePrimitive);
344 | const mAddyOsmani = addyOsmani(fibonacciMultiplePrimitive);
345 | const mMemoizerific = memoizerific(Infinity)(fibonacciMultiplePrimitive);
346 | const mLruMemoize = lruMemoize(Infinity)(fibonacciMultiplePrimitive);
347 | const mMoize = moize(fibonacciMultiplePrimitive);
348 | const mMicroMemoize = microMemoize(fibonacciMultiplePrimitive);
349 | const mIMemoized = iMemoized.memoize(fibonacciMultiplePrimitive);
350 | const mNano = nanomemoize(fibonacciMultiplePrimitive);
351 | const mMemize = memize(fibonacciMultiplePrimitive);
352 |
353 | return new Promise((resolve) => {
354 | fibonacciSuite
355 | .add('nano-memoize', () => {
356 | mNano(fibonacciNumber, isComplete);
357 | })
358 | .add('addy-osmani', () => {
359 | mAddyOsmani(fibonacciNumber, isComplete);
360 | })
361 | .add('lru-memoize', () => {
362 | mLruMemoize(fibonacciNumber, isComplete);
363 | })
364 | .add('memoizee', () => {
365 | mMemoizee(fibonacciNumber, isComplete);
366 | })
367 | .add('iMemoized', () => {
368 | mIMemoized(fibonacciNumber, isComplete);
369 | })
370 | .add('memoizerific', () => {
371 | mMemoizerific(fibonacciNumber, isComplete);
372 | })
373 | .add('fast-memoize', () => {
374 | mFastMemoize(fibonacciNumber, isComplete);
375 | })
376 | .add('micro-memoize', () => {
377 | mMicroMemoize(fibonacciNumber, isComplete);
378 | })
379 | .add('moize', () => {
380 | mMoize(fibonacciNumber, isComplete);
381 | })
382 | .add('memize', () => {
383 | mMemize(fibonacciNumber, isComplete);
384 | })
385 | .on('start', () => {
386 | console.log(''); // eslint-disable-line no-console
387 | console.log('Starting cycles for functions with multiple parameters that contain only primitives...'); // eslint-disable-line no-console
388 |
389 | results = [];
390 |
391 | spinner.start();
392 | })
393 | .on('cycle', onCycle)
394 | .on('complete', () => {
395 | onComplete();
396 | resolve();
397 | })
398 | .run({
399 | async: true
400 | });
401 | });
402 | };
403 |
404 | const runMultipleObjectSuite = () => {
405 | const fibonacciSuite = new Benchmark.Suite('Multiple object parameter');
406 | const fibonacciNumber = {number:35};
407 | const check = {};
408 |
409 | const mUnderscore = underscore(fibonacciMultipleObject);
410 | // const mRamda = ramda(fibonacciMultipleObject);
411 | const mMemoizee = memoizee(fibonacciMultipleObject);
412 | const mFastMemoize = fastMemoize(fibonacciMultipleObject);
413 | const mAddyOsmani = addyOsmani(fibonacciMultipleObject);
414 | const mMemoizerific = memoizerific(Infinity)(fibonacciMultipleObject);
415 | const mLruMemoize = lruMemoize(Infinity)(fibonacciMultipleObject);
416 | const mMoize = moize(fibonacciMultipleObject);
417 | const mMicroMemoize = microMemoize(fibonacciMultipleObject);
418 | const mIMemoized = iMemoized.memoize(fibonacciMultipleObject);
419 | const mNano = nanomemoize(fibonacciMultipleObject);
420 | const mMemize = memize(fibonacciMultipleObject);
421 |
422 |
423 | return new Promise((resolve) => {
424 | fibonacciSuite
425 | .add('nano-memoize', () => {
426 | mNano(fibonacciNumber,check);
427 | })
428 | .add('addy-osmani', () => {
429 | mAddyOsmani(fibonacciNumber,check);
430 | })
431 | .add('lru-memoize', () => {
432 | mLruMemoize(fibonacciNumber,check);
433 | })
434 | .add('memoizee', () => {
435 | mMemoizee(fibonacciNumber,check);
436 | })
437 | .add('memoizerific', () => {
438 | mMemoizerific(fibonacciNumber,check);
439 | })
440 | /*.add('ramda', () => {
441 | mRamda(fibonacciNumber);
442 | })*/
443 | .add('underscore', () => {
444 | mUnderscore(fibonacciNumber,check);
445 | })
446 | .add('iMemoized', () => {
447 | mIMemoized(fibonacciNumber,check);
448 | })
449 | .add('micro-memoize', () => {
450 | mMicroMemoize(fibonacciNumber,check);
451 | })
452 | .add('moize', () => {
453 | mMoize(fibonacciNumber,check);
454 | })
455 | .add('fast-memoize', () => {
456 | mFastMemoize(fibonacciNumber,check);
457 | })
458 | .add('memize', () => {
459 | mMemize(fibonacciNumber, check);
460 | })
461 | .on('start', () => {
462 | console.log(''); // eslint-disable-line no-console
463 | console.log('Starting cycles for functions with multiple object parameters...'); // eslint-disable-line no-console
464 |
465 | results = [];
466 |
467 | spinner.start();
468 | })
469 | .on('cycle', onCycle)
470 | .on('complete', () => {
471 | onComplete();
472 | resolve();
473 | })
474 | .run({
475 | async: true
476 | });
477 | });
478 | };
479 |
480 | const runMultipleMixedSuite = () => {
481 | const fibonacciSuite = new Benchmark.Suite('Multiple mixed parameters');
482 | const fibonacciNumber = 35;
483 | const isComplete = {
484 | isComplete: false
485 | };
486 |
487 | const mMemoizee = memoizee(fibonacciMultipleMixed);
488 | const mFastMemoize = fastMemoize(fibonacciMultipleMixed);
489 | const mAddyOsmani = addyOsmani(fibonacciMultipleMixed);
490 | const mMemoizerific = memoizerific(Infinity)(fibonacciMultipleMixed);
491 | const mLruMemoize = lruMemoize(Infinity)(fibonacciMultipleMixed);
492 | const mMoize = moize(fibonacciMultipleMixed);
493 | const mMicroMemoize = microMemoize(fibonacciMultipleMixed);
494 | const mNano = nanomemoize(fibonacciMultipleMixed);
495 | const mMemize = memize(fibonacciMultipleMixed);
496 |
497 | return new Promise((resolve) => {
498 | fibonacciSuite
499 | .add('nano-memoize', () => {
500 | mNano(fibonacciNumber,isComplete);
501 | })
502 | .add('addy-osmani', () => {
503 | mAddyOsmani(fibonacciNumber, isComplete);
504 | })
505 | .add('lru-memoize', () => {
506 | mLruMemoize(fibonacciNumber, isComplete);
507 | })
508 | .add('memoizee', () => {
509 | mMemoizee(fibonacciNumber, isComplete);
510 | })
511 | .add('memoizerific', () => {
512 | mMemoizerific(fibonacciNumber, isComplete);
513 | })
514 | .add('fast-memoize', () => {
515 | mFastMemoize(fibonacciNumber, isComplete);
516 | })
517 | .add('micro-memoize', () => {
518 | mMicroMemoize(fibonacciNumber, isComplete);
519 | })
520 | .add('moize', () => {
521 | mMoize(fibonacciNumber, isComplete);
522 | })
523 | .add('memize', () => {
524 | mMemize(fibonacciNumber, isComplete);
525 | })
526 | .on('start', () => {
527 | console.log(''); // eslint-disable-line no-console
528 | console.log('Starting cycles for functions with multiple mixed parameters ...'); // eslint-disable-line no-console
529 |
530 | results = [];
531 |
532 | spinner.start();
533 | })
534 | .on('cycle', onCycle)
535 | .on('complete', () => {
536 | onComplete();
537 | resolve();
538 | })
539 | .run({
540 | async: true
541 | });
542 | });
543 | };
544 |
545 | const runAlternativeOptionsSuite = () => {
546 | const fibonacciSuite = new Benchmark.Suite('Alternative single object');
547 | const fibonacciNumber = {
548 | number: 35
549 | };
550 |
551 | const mMicroMemoizeDeepEquals = microMemoize(fibonacciSingleObject, {
552 | isEqual: deepEquals
553 | });
554 |
555 | const mMicroMemoizeFastEquals = microMemoize(fibonacciSingleObject, {
556 | isEqual: fastEquals
557 | });
558 |
559 | const mMicroMemoizeFastDeepEquals = microMemoize(fibonacciSingleObject, {
560 | isEqual: fastDeepEqual
561 | });
562 |
563 |
564 | const mMoizeDeep = moize(fibonacciSingleObject, {
565 | isDeepEqual: true
566 | });
567 |
568 | const mMoizeDeepEquals = moize(fibonacciSingleObject, {
569 | matchesArg: deepEquals
570 | });
571 |
572 | const mMoizeFastEquals = moize(fibonacciSingleObject, {
573 | matchesArg: fastEquals
574 | });
575 |
576 | const mMoizeFastDeepEquals = moize(fibonacciSingleObject, {
577 | matchesArg: fastDeepEqual
578 | });
579 |
580 |
581 | const mNanoDeepEquals = nanomemoize(fibonacciSingleObject, {
582 | equals: deepEquals
583 | });
584 | const mNanoFastEquals = nanomemoize(fibonacciSingleObject, {
585 | equals: fastEquals
586 | });
587 | const mNanoFastDeepEquals = nanomemoize(fibonacciSingleObject, {
588 | equals: fastDeepEqual
589 | });
590 |
591 |
592 | return new Promise((resolve) => {
593 | fibonacciSuite
594 | .add('nanomemoize deep equals (lodash isEqual)', () => {
595 | mNanoDeepEquals(fibonacciNumber);
596 | })
597 | .add('nanomemoize fast equals (fast-equals deep)', () => {
598 | mNanoFastEquals(fibonacciNumber);
599 | })
600 | .add('nanomemoize fast equals (fast-deep-equals)', () => {
601 | mNanoFastDeepEquals(fibonacciNumber);
602 | })
603 | .add('micro-memoize deep equals (lodash isEqual)', () => {
604 | mMicroMemoizeDeepEquals(fibonacciNumber);
605 | })
606 | .add('micro-memoize deep equals (fast-equals)', () => {
607 | mMicroMemoizeFastEquals(fibonacciNumber);
608 | })
609 | .add('micro-memoize deep equals (fast-deep-equal)', () => {
610 | mMicroMemoizeFastDeepEquals(fibonacciNumber);
611 | })
612 | .add('moize deep (internal)', () => {
613 | mMoizeDeep(fibonacciNumber);
614 | })
615 | .add('moize deep equals (lodash isEqual)', () => {
616 | mMoizeDeepEquals(fibonacciNumber);
617 | })
618 | .add('moize deep equals (fast-equals)', () => {
619 | mMoizeFastEquals(fibonacciNumber);
620 | })
621 | .add('moize deep equals (fast-deep-equal)', () => {
622 | mMoizeFastEquals(fibonacciNumber);
623 | })
624 |
625 | .on('start', () => {
626 | console.log(''); // eslint-disable-line no-console
627 | console.log('Starting cycles for alternative cache types...'); // eslint-disable-line no-console
628 |
629 | results = [];
630 |
631 | spinner.start();
632 | })
633 | .on('abort',(...args) => {
634 | console.log('abort',args);
635 | })
636 | .on('cycle', onCycle)
637 | .on('complete', () => {
638 | onComplete();
639 | resolve();
640 | })
641 | .run({
642 | async: true
643 | });
644 | });
645 | };
646 |
647 | // runAlternativeOptionsSuite();
648 |
649 | //runMultiplePrimitiveSuite();
650 |
651 | runSingleParameterSuite()
652 | .then(runSingleParameterObjectSuite)
653 | .then(runMultiplePrimitiveSuite)
654 | .then(runMultipleObjectSuite)
655 | .then(runMultipleMixedSuite)
656 | .then(runAlternativeOptionsSuite);
657 |
--------------------------------------------------------------------------------
/benchmark/micro-memoize.min.js:
--------------------------------------------------------------------------------
1 | (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory():typeof define==="function"&&define.amd?define(factory):(global=global||self,global["micro-memoize"]=factory())})(this,function(){"use strict";var DEFAULT_OPTIONS_KEYS={isEqual:true,isMatchingKey:true,isPromise:true,maxSize:true,onCacheAdd:true,onCacheChange:true,onCacheHit:true,transformKey:true};function createGetKeyIndex(_a){var isEqual=_a.isEqual,isMatchingKey=_a.isMatchingKey,maxSize=_a.maxSize;if(typeof isMatchingKey==="function"){return function getKeyIndex(allKeys,keyToMatch){if(isMatchingKey(allKeys[0],keyToMatch)){return 0}if(maxSize>1){var keysLength=allKeys.length;for(var index=1;index1){return function getKeyIndex(allKeys,keyToMatch){var keysLength=allKeys.length;var keyLength=keyToMatch.length;var existingKey;for(var index=0;index=maxSize){cache.keys.length=maxSize;cache.values.length=maxSize}}function createUpdateAsyncCache(options){var getKeyIndex=createGetKeyIndex(options);var onCacheChange=options.onCacheChange,onCacheHit=options.onCacheHit;var shouldUpdateOnChange=typeof onCacheChange==="function";var shouldUpdateOnHit=typeof onCacheHit==="function";return function(cache,memoized){var key=cache.keys[0];cache.values[0]=cache.values[0].then(function(value){shouldUpdateOnHit&&onCacheHit(cache,options,memoized);shouldUpdateOnChange&&onCacheChange(cache,options,memoized);return value}).catch(function(error){var keyIndex=getKeyIndex(cache.keys,key);if(keyIndex!==-1){cache.keys.splice(keyIndex,1);cache.values.splice(keyIndex,1)}throw error})}}var slice=Array.prototype.slice;var defineProperties=Object.defineProperties;function createMemoizedFunction(fn,options){if(options===void 0){options={}}if(fn.isMemoized){return fn}var _a=options.isEqual,isEqual=_a===void 0?isSameValueZero:_a,isMatchingKey=options.isMatchingKey,_b=options.isPromise,isPromise=_b===void 0?false:_b,_c=options.maxSize,maxSize=_c===void 0?1:_c,onCacheAdd=options.onCacheAdd,onCacheChange=options.onCacheChange,onCacheHit=options.onCacheHit,transformKey=options.transformKey;var normalizedOptions=mergeOptions(getCustomOptions(options),{isEqual:isEqual,isMatchingKey:isMatchingKey,isPromise:isPromise,maxSize:maxSize,onCacheAdd:onCacheAdd,onCacheChange:onCacheChange,onCacheHit:onCacheHit,transformKey:transformKey});var getKeyIndex=createGetKeyIndex(normalizedOptions);var updateAsyncCache=createUpdateAsyncCache(normalizedOptions);var keys=[];var values=[];var cache={keys:keys,get size(){return cache.keys.length},values:values};var canTransformKey=typeof transformKey==="function";var shouldCloneArguments=!!(transformKey||isMatchingKey);var shouldUpdateOnAdd=typeof onCacheAdd==="function";var shouldUpdateOnChange=typeof onCacheChange==="function";var shouldUpdateOnHit=typeof onCacheHit==="function";function memoized(){var normalizedArgs=shouldCloneArguments?slice.call(arguments,0):arguments;var key=canTransformKey?transformKey(normalizedArgs):normalizedArgs;var keyIndex=keys.length?getKeyIndex(keys,key):-1;if(keyIndex!==-1){shouldUpdateOnHit&&onCacheHit(cache,normalizedOptions,memoized);if(keyIndex){orderByLru(cache,keys[keyIndex],values[keyIndex],keyIndex,maxSize);shouldUpdateOnChange&&onCacheChange(cache,normalizedOptions,memoized)}}else{var newValue=fn.apply(this,arguments);var newKey=shouldCloneArguments?key:slice.call(arguments,0);orderByLru(cache,newKey,newValue,keys.length,maxSize);isPromise&&updateAsyncCache(cache,memoized);shouldUpdateOnAdd&&onCacheAdd(cache,normalizedOptions,memoized);shouldUpdateOnChange&&onCacheChange(cache,normalizedOptions,memoized)}return values[0]}defineProperties(memoized,{cache:{configurable:true,value:cache},cacheSnapshot:{configurable:true,get:function(){return{keys:slice.call(cache.keys,0),size:cache.size,values:slice.call(cache.values,0)}}},isMemoized:{configurable:true,value:true},options:{configurable:true,value:normalizedOptions}});return memoized}return createMemoizedFunction});
--------------------------------------------------------------------------------
/benchmark/micro-memoize.min.js.br:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anywhichway/nano-memoize/b51957dfad2d7a85531b622ecb9e9661db785d82/benchmark/micro-memoize.min.js.br
--------------------------------------------------------------------------------
/benchmark/micro-memoize.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anywhichway/nano-memoize/b51957dfad2d7a85531b622ecb9e9661db785d82/benchmark/micro-memoize.min.js.gz
--------------------------------------------------------------------------------
/benchmark/real-world-simulation.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 | const vm = require("node:vm");
3 | const v8 = require("v8");
4 |
5 | v8.setFlagsFromString('--expose_gc');
6 | const gc = vm.runInNewContext('gc');
7 |
8 | const Benchmark = require('benchmark');
9 | const assert = require('assert');
10 | const Table = require('cli-table');
11 | const ora = require('ora');
12 |
13 | const showResults = (benchmarkResults) => {
14 | const table = new Table({
15 | head: ['Name', 'Ops / sec', 'Relative margin of error', 'Sample size']
16 | });
17 |
18 | benchmarkResults.forEach((result) => {
19 | const name = result.target.name;
20 | const opsPerSecond = result.target.hz.toLocaleString('en-US', {
21 | maximumFractionDigits: 0
22 | });
23 | const relativeMarginOferror = `± ${result.target.stats.rme.toFixed(2)}%`;
24 | const sampleSize = result.target.stats.sample.length;
25 |
26 | table.push([name, opsPerSecond, relativeMarginOferror, sampleSize]);
27 | });
28 |
29 | console.log(table.toString()); // eslint-disable-line no-console
30 | };
31 |
32 | const sortDescResults = (benchmarkResults) => {
33 | return benchmarkResults.sort((a, b) => {
34 | return a.target.hz < b.target.hz ? 1 : -1;
35 | });
36 | };
37 |
38 | const spinner = ora('Running benchmark');
39 |
40 | const nanomemoize = require('../dist/nano-memoize.js').default;
41 | const fastMemoize = require('fast-memoize');
42 | const microMemoize = require('micro-memoize');
43 | const moize = require('moize');
44 | const memize = require('memize');
45 |
46 | const fibonacciMultipleMixed = (number, check={}) => {
47 | if (check.isComplete) {
48 | return number;
49 | }
50 |
51 | const firstValue = number - 1;
52 | const secondValue = number - 2;
53 |
54 | return (
55 | fibonacciMultipleMixed(firstValue, {
56 | isComplete: firstValue < 2
57 | }) +
58 | fibonacciMultipleMixed(secondValue, {
59 | isComplete: secondValue < 2
60 | })
61 | );
62 | }
63 |
64 | const manyArgsToString = (...args) => {
65 | return JSON.stringify(args);
66 | }
67 |
68 | const sumManyArgs = (...args) => {
69 | return args.reduce((acc, curr) => {
70 | return acc + curr;
71 | }, 0);
72 | }
73 |
74 |
75 | // Memoize each expensive function
76 | const nanomemoizedFunction1 = nanomemoize(fibonacciMultipleMixed);
77 | const fastMemoizedFunction1 = fastMemoize(fibonacciMultipleMixed);
78 | const microMemoizedFunction1 = microMemoize(fibonacciMultipleMixed);
79 | const moizeMemoizedFunction1 = microMemoize(fibonacciMultipleMixed);
80 | const memizeMemoizedFunction1 = memize(fibonacciMultipleMixed);
81 | const nanomemoizedFunction2 = nanomemoize(manyArgsToString);
82 | const fastMemoizedFunction2 = fastMemoize(manyArgsToString);
83 | const microMemoizedFunction2 = microMemoize(manyArgsToString);
84 | const moizeMemoizedFunction2 = microMemoize(manyArgsToString);
85 | const memizeMemoizedFunction2 = memize(manyArgsToString);
86 | const nanomemoizedFunction3 = nanomemoize(sumManyArgs);
87 | const fastMemoizedFunction3 = fastMemoize(sumManyArgs);
88 | const microMemoizedFunction3 = microMemoize(sumManyArgs);
89 | const moizeMemoizedFunction3 = microMemoize(sumManyArgs);
90 | const memizeMemoizedFunction3 = memize(sumManyArgs);
91 |
92 |
93 |
94 | // Set up the benchmark test
95 | const suite = new Benchmark.Suite();
96 |
97 | let results = [];
98 |
99 | const onCycle = (event) => {
100 | results.push(event);
101 | ora(event.target.name).succeed();
102 | gc();
103 | };
104 |
105 | const onComplete = () => {
106 | spinner.stop();
107 |
108 | const orderedBenchmarkResults = sortDescResults(results);
109 |
110 | showResults(orderedBenchmarkResults);
111 | };
112 | // Add the tests for each memoized function
113 | suite
114 | .add('nanomemoizedFunctions', () => {
115 | const random = Math.round(Math.random()*10);
116 | let args1, args2
117 | if(random<=2) {
118 | args1 = [5,{}];
119 | args2 = [1,2,3,4,5,6,7,8,9,10];
120 | } else {
121 | args1 = [random,{}];
122 | args2 = [].fill(random,0,10);
123 | }
124 | nanomemoizedFunction1.apply(null,args1);
125 | const result = nanomemoizedFunction2.apply(null,args2);
126 | nanomemoizedFunction3.apply(null,args2);
127 | const shouldBe = manyArgsToString.apply(null,args2);
128 | if(result!==shouldBe) console.log("err nanomemoizedFunction2")
129 | //assert.strictEqual(result,shouldBe);
130 | })
131 | .add('fastMemoizedFunctions', () => {
132 | const random = Math.round(Math.random()*10);
133 | let args1, args2;
134 | if(random<=2) {
135 | args1 = [5,{}];
136 | args2 = [1,2,3,4,5,6,7,8,9,10];
137 | } else {
138 | args1 = [random,{}];
139 | args2 = [].fill(random,0,10);
140 | }
141 | const result = fastMemoizedFunction1.apply(null,args1);
142 | fastMemoizedFunction2.apply(null,args2);
143 | fastMemoizedFunction3.apply(null,args2);
144 | //const shouldBe = fibonacciMultipleMixed.apply(null,args);
145 | //if(result!==shouldBe) console.log("err nanomemoizedFunction")
146 | //assert.strictEqual(result,shouldBe);
147 | })
148 | .add('microMemoizedFunctions', () => {
149 | const random = Math.round(Math.random()*10);
150 | let args1, args2;
151 | if(random<=2) {
152 | args1 = [5,{}];
153 | args2 = [1,2,3,4,5,6,7,8,9,10];
154 | } else {
155 | args1 = [random,{}];
156 | args2 = [].fill(random,0,10);
157 | }
158 | const result = microMemoizedFunction1.apply(null,args1);
159 | microMemoizedFunction2.apply(null,args2);
160 | microMemoizedFunction3.apply(null,args2);
161 | //const shouldBe = fibonacciMultipleMixed.apply(null,args);
162 | //if(result!==shouldBe) console.log("err nanomemoizedFunction")
163 | //assert.strictEqual(result,shouldBe);
164 | })
165 | .add('moizeMemoizedFunctions', () => {
166 | const random = Math.round(Math.random()*10);
167 | let args1, args2;
168 | if(random<=2) {
169 | args1 = [5,{}];
170 | args2 = [1,2,3,4,5,6,7,8,9,10];
171 | } else {
172 | args1 = [random,{}];
173 | args2 = [].fill(random,0,10);
174 | }
175 | const result = moizeMemoizedFunction1.apply(null,args1);
176 | moizeMemoizedFunction2.apply(null,args2);
177 | moizeMemoizedFunction3.apply(null,args2);
178 | //const shouldBe = fibonacciMultipleMixed.apply(null,args);
179 | //if(result!==shouldBe) console.log("err nanomemoizedFunction")
180 | //assert.strictEqual(result,shouldBe);
181 | })
182 | .add('memizeMemoizedFunctions', () => {
183 | const random = Math.round(Math.random()*10);
184 | let args1, args2;
185 | if(random<=2) {
186 | args1 = [5,{}];
187 | args2 = [1,2,3,4,5,6,7,8,9,10];
188 | } else {
189 | args1 = [random,{}];
190 | args2 = [].fill(random,0,10);
191 | }
192 | const result = memizeMemoizedFunction1.apply(null,args1);
193 | memizeMemoizedFunction2.apply(null,args2);
194 | memizeMemoizedFunction3.apply(null,args2);
195 | //const shouldBe = fibonacciMultipleMixed.apply(null,args);
196 | //if(result!==shouldBe) console.log("err nanomemoizedFunction")
197 | //assert.strictEqual(result,shouldBe);
198 | })
199 | .on('start', () => {
200 | console.log(''); // eslint-disable-line no-console
201 | console.log('Starting real world simulation...'); // eslint-disable-line no-console
202 | results = [];
203 | spinner.start();
204 | })
205 | .on('cycle', onCycle)
206 | .on('complete', () => {
207 | onComplete();
208 | //resolve();
209 | })
210 | .run({
211 | async: true
212 | });
213 |
--------------------------------------------------------------------------------
/dist/nano-memoize.js:
--------------------------------------------------------------------------------
1 | function $parcel$defineInteropFlag(a) {
2 | Object.defineProperty(a, '__esModule', {value: true, configurable: true});
3 | }
4 | function $parcel$export(e, n, v, s) {
5 | Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
6 | }
7 |
8 | $parcel$defineInteropFlag(module.exports);
9 |
10 | $parcel$export(module.exports, "nanomemoize", () => $4fa36e821943b400$export$22f15dd4e5be7e52);
11 | $parcel$export(module.exports, "default", () => $4fa36e821943b400$export$2e2bcd8739ae039);
12 | /*
13 | MIT License
14 |
15 | Copyright (c) 2018-2023 Simon Y. Blackwell
16 |
17 | Permission is hereby granted, free of charge, to any person obtaining a copy
18 | of this software and associated documentation files (the "Software"), to deal
19 | in the Software without restriction, including without limitation the rights
20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 | copies of the Software, and to permit persons to whom the Software is
22 | furnished to do so, subject to the following conditions:
23 |
24 | The above copyright notice and this permission notice shall be included in all
25 | copies or substantial portions of the Software.
26 |
27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 | SOFTWARE.
34 | */ function $4fa36e821943b400$var$vrgs(f) {
35 | var s = f + "", i = s.indexOf("...");
36 | return i >= 0 && (i < s.indexOf(")") || s.indexOf("arguments") >= 0);
37 | }
38 | function $4fa36e821943b400$export$22f15dd4e5be7e52(fn, o) {
39 | /*o = {
40 | serializer, // used to serialize arguments of single argument functions, multis are not serialized
41 | equals, // equality tester, will force use of slower multiarg approach even for single arg functions
42 | maxAge, // max cache age is ms, set > 0 && < Infinity if you want automatic clearing
43 | maxArgs, // max args to use for signature
44 | vargs = vrgs(fn) // set to true if function may have variable or beyond-signature arguments, default is best attempt at infering
45 | } = {}
46 | */ o || (o = {});
47 | var vargs = o.vargs || $4fa36e821943b400$var$vrgs(fn), k = [], cache = new Map(), u, to, d = function(key) {
48 | return to = setTimeout(function() {
49 | if (u) {
50 | cache.delete(key);
51 | return;
52 | }
53 | // dealing with multi-arg function, c and k are Arrays
54 | k.splice(key, 1);
55 | //v.splice(key,1);
56 | }, o.maxAge);
57 | }, c = o.maxAge > 0 && o.maxAge < Infinity ? d : 0, eq = o.equals ? o.equals : 0, maxargs = o.maxArgs, srlz = o.serializer, f; // memoized function to return
58 | if (fn.length === 1 && !o.equals && !vargs) {
59 | // for single argument functions, just use a Map lookup
60 | f = function(a) {
61 | if (srlz) a = srlz(a);
62 | var r;
63 | return cache.get(a) || (!c || c(a), cache.set(a, r = fn.call(this, a)), r);
64 | };
65 | u = 1;
66 | } else if (eq) // for multiple arg functions, loop through a cache of all the args
67 | // looking at each arg separately so a test can abort as soon as possible
68 | f = function() {
69 | var l = maxargs || arguments.length, kl = k.length, i = -1;
70 | while(++i < kl)if (k[i].length === l) {
71 | var j = -1;
72 | while(++j < l && eq(arguments[j], k[i][j])); // compare each arg
73 | if (j === l) return k[i].val //the args matched;
74 | ;
75 | }
76 | // set change timeout only when new value computed, hits will not push out the tte, but it is arguable they should not
77 | k[i] = arguments;
78 | return !c || c(i), arguments.val = fn.apply(this, k[i]);
79 | };
80 | else f = function() {
81 | var l = maxargs || arguments.length, kl = k.length, i = -1;
82 | while(++i < kl)if (k[i].length === l) {
83 | var j = -1;
84 | while(++j < l && arguments[j] === k[i][j]); // compare each arg
85 | if (j === l) return k[i].val //the args matched;
86 | ;
87 | }
88 | // set change timeout only when new value computed, hits will not push out the tte, but it is arguable they should not
89 | k[i] = arguments;
90 | return !c || c(i), arguments.val = fn.apply(this, k[i]);
91 | };
92 | // reset all the caches
93 | f.clear = function() {
94 | if (to) clearTimeout(to);
95 | cache.clear();
96 | k = [];
97 | };
98 | f.keys = function() {
99 | return u ? [
100 | ...cache.keys()
101 | ] : k.slice();
102 | };
103 | f.values = function() {
104 | return u ? [
105 | ...cache.values()
106 | ] : k.map((args)=>args.val);
107 | };
108 | return f;
109 | }
110 | var $4fa36e821943b400$export$2e2bcd8739ae039 = $4fa36e821943b400$export$22f15dd4e5be7e52;
111 |
112 |
113 | //# sourceMappingURL=nano-memoize.js.map
114 |
--------------------------------------------------------------------------------
/dist/nano-memoize.js.map:
--------------------------------------------------------------------------------
1 | {"mappings":";;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;AAsBA,GAEA,SAAS,2BAAK,CAAC;IACb,IAAI,IAAI,IAAE,IACT,IAAI,EAAE,QAAQ;IACf,OAAO,KAAG,KAAM,CAAA,IAAE,EAAE,QAAQ,QAAQ,EAAE,QAAQ,gBAAc,CAAA;AAC9D;AACA,SAAS,0CAAY,EAAE,EAAC,CAAC;IACxB;;;;;;;CAOA,GACA,KAAM,CAAA,IAAE,CAAC,CAAA;IACT,IAAI,QAAQ,EAAE,SAAS,2BAAK,KAC3B,IAAI,EAAE,EACN,QAAQ,IAAI,OACZ,GACA,IACA,IAAI,SAAS,GAAG;QAAI,OAAO,KAAK,WAAW;YAC1C,IAAG,GAAG;gBACL,MAAM,OAAO;gBACb;YACD;YACA,sDAAsD;YACtD,EAAE,OAAQ,KAAI;QACd,kBAAkB;QAClB,GAAE,EAAE;IACL,GACA,IAAI,EAAE,SAAO,KAAK,EAAE,SAAO,WAAW,IAAI,GAC1C,KAAK,EAAE,SAAS,EAAE,SAAS,GAC3B,UAAU,EAAE,SACZ,OAAO,EAAE,YACT,GAAG,8BAA8B;IAClC,IAAG,GAAG,WAAS,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO;QACxC,uDAAuD;QACvD,IAAK,SAAS,CAAC;YACb,IAAG,MAAM,IAAI,KAAK;YAClB,IAAI;YACJ,OAAO,MAAM,IAAI,MAAO,CAAA,AAAC,CAAC,KAAG,EAAE,IAAI,MAAM,IAAI,GAAE,IAAI,GAAG,KAAK,IAAI,EAAE,KAAI,CAAA;QACvE;QACA,IAAI;IACL,OAAQ,IAAG,IACX,mEAAmE;IACnE,yEAAyE;IACxE,IAAI;QACH,IAAI,IAAI,WAAS,UAAU,QAAQ,KAAK,EAAE,QAAO,IAAI;QACrD,MAAM,EAAE,IAAE,GACT,IAAI,CAAC,CAAC,EAAE,CAAC,WAAW,GAAG;YACtB,IAAI,IAAI;YACR,MAAQ,EAAE,IAAI,KAAK,GAAG,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAM,mBAAmB;YACpE,IAAI,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,mBAAmB;;QACjD;QAED,sHAAsH;QACtH,CAAC,CAAC,EAAE,GAAG;QACP,OAAO,AAAC,CAAC,KAAG,EAAE,IAAI,UAAU,MAAM,GAAG,MAAM,IAAI,EAAC,CAAC,CAAC,EAAE;IACrD;SAEA,IAAI;QACH,IAAI,IAAI,WAAS,UAAU,QAAQ,KAAK,EAAE,QAAQ,IAAI;QACtD,MAAM,EAAE,IAAE,GACT,IAAI,CAAC,CAAC,EAAE,CAAC,WAAW,GAAG;YACtB,IAAI,IAAI;YACR,MAAO,EAAE,IAAI,KAAK,SAAS,CAAC,EAAE,KAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAK,mBAAmB;YAChE,IAAI,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,mBAAmB;;QACjD;QAED,sHAAsH;QACtH,CAAC,CAAC,EAAE,GAAG;QACP,OAAO,AAAC,CAAC,KAAG,EAAE,IAAI,UAAU,MAAM,GAAG,MAAM,IAAI,EAAC,CAAC,CAAC,EAAE;IACrD;IAED,uBAAuB;IACvB,EAAE,QAAQ;QACT,IAAG,IAAI,aAAa;QACpB,MAAM;QACN,IAAI,EAAE;IACP;IACA,EAAE,OAAO;QAAa,OAAO,IAAI;eAAI,MAAM;SAAO,GAAG,EAAE;IAAS;IAChE,EAAE,SAAS;QAAa,OAAO,IAAI;eAAI,MAAM;SAAS,GAAG,EAAE,IAAI,CAAC,OAAS,KAAK;IAAM;IACpF,OAAO;AACR;IAGA,2CAAe","sources":["src/index.js"],"sourcesContent":["/*\nMIT License\n\nCopyright (c) 2018-2023 Simon Y. Blackwell\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\n\tThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n\tFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n\tOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\nfunction vrgs(f) {\n\t\tvar s = f+\"\",\n\t\t\ti = s.indexOf(\"...\");\n\t\treturn i>=0 && (i=0);\n}\nfunction nanomemoize(fn,o) {\n\t/*o = {\n\t\tserializer, // used to serialize arguments of single argument functions, multis are not serialized\n\t\tequals, // equality tester, will force use of slower multiarg approach even for single arg functions\n\t\tmaxAge, // max cache age is ms, set > 0 && < Infinity if you want automatic clearing\n\t\tmaxArgs, // max args to use for signature\n\t\tvargs = vrgs(fn) // set to true if function may have variable or beyond-signature arguments, default is best attempt at infering\n\t } = {}\n\t*/\n\to || (o={});\n\tvar vargs = o.vargs || vrgs(fn),\n\t\tk = [], // multiple arg function arg key cache\n\t\tcache = new Map(), // single arg function key/value cache\n\t\tu, // flag indicating a unary arg function is in use for clear operation\n\t\tto, // timeout for clearing cache\n\t\td = function(key) { return to = setTimeout(function() {\n\t\t\tif(u) {\n\t\t\t\tcache.delete(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// dealing with multi-arg function, c and k are Arrays\n\t\t\tk.splice (key,1);\n\t\t\t//v.splice(key,1);\n\t\t\t},o.maxAge);\n\t\t},\n\t\tc = o.maxAge>0 && o.maxAge args.val); };\n\treturn f;\n}\nexport {nanomemoize}\n\nexport default nanomemoize;\n"],"names":[],"version":3,"file":"nano-memoize.js.map"}
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | type Func = (...args: any[]) => any
2 |
3 | declare module "nano-memoize" {
4 | interface BasicHelpers
5 | {
6 | clear(): void;
7 | values(): ReturnType[]
8 | }
9 |
10 | interface KeysHelpers{
11 | keys(): K[]
12 | }
13 |
14 | export function nanomemoize>(
15 | fn: T,
16 | options?: {
17 | /**
18 | * Only use the provided maxArgs for cache look-up, useful for ignoring final callback arguments
19 | */
20 | maxArgs?: number;
21 | /**
22 | * Number of milliseconds to cache a result, set to `Infinity` to never create timers or expire
23 | */
24 | maxAge?: number;
25 | /**
26 | * The serializer/key generator to use for single argument functions (optional, not recommended)
27 | * must be able to serialize objects and functions, by default a WeakMap is used internally without serializing
28 | */
29 | serializer?: (...args: Parameters) => K;
30 | /**
31 | * the equals function to use for multi-argument functions (optional, try to avoid) e.g. deepEquals for objects
32 | */
33 | equals?: (...args: any[]) => boolean;
34 | /**
35 | * Forces the use of multi-argument paradigm, auto set if function has a spread argument or uses `arguments` in its body.
36 | */
37 | vargs?: boolean;
38 | }
39 | ): T & BasicHelpers & KeysHelpers;
40 |
41 | export default nanomemoize;
42 | }
43 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2018-2023 Simon Y. Blackwell
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */ function $cf838c15c8b009ba$var$vrgs(f) {
24 | var s = f + "", i = s.indexOf("...");
25 | return i >= 0 && (i < s.indexOf(")") || s.indexOf("arguments") >= 0);
26 | }
27 | function $cf838c15c8b009ba$export$22f15dd4e5be7e52(fn, o) {
28 | /*o = {
29 | serializer, // used to serialize arguments of single argument functions, multis are not serialized
30 | equals, // equality tester, will force use of slower multiarg approach even for single arg functions
31 | maxAge, // max cache age is ms, set > 0 && < Infinity if you want automatic clearing
32 | maxArgs, // max args to use for signature
33 | vargs = vrgs(fn) // set to true if function may have variable or beyond-signature arguments, default is best attempt at infering
34 | } = {}
35 | */ o || (o = {});
36 | var vargs = o.vargs || $cf838c15c8b009ba$var$vrgs(fn), k = [], cache = new Map(), u, to, d = function(key) {
37 | return to = setTimeout(function() {
38 | if (u) {
39 | cache.delete(key);
40 | return;
41 | }
42 | // dealing with multi-arg function, c and k are Arrays
43 | k.splice(key, 1);
44 | //v.splice(key,1);
45 | }, o.maxAge);
46 | }, c = o.maxAge > 0 && o.maxAge < Infinity ? d : 0, eq = o.equals ? o.equals : 0, maxargs = o.maxArgs, srlz = o.serializer, f; // memoized function to return
47 | if (fn.length === 1 && !o.equals && !vargs) {
48 | // for single argument functions, just use a Map lookup
49 | f = function(a) {
50 | if (srlz) a = srlz(a);
51 | var r;
52 | return cache.get(a) || (!c || c(a), cache.set(a, r = fn.call(this, a)), r);
53 | };
54 | u = 1;
55 | } else if (eq) // for multiple arg functions, loop through a cache of all the args
56 | // looking at each arg separately so a test can abort as soon as possible
57 | f = function() {
58 | var l = maxargs || arguments.length, kl = k.length, i = -1;
59 | while(++i < kl)if (k[i].length === l) {
60 | var j = -1;
61 | while(++j < l && eq(arguments[j], k[i][j])); // compare each arg
62 | if (j === l) return k[i].val //the args matched;
63 | ;
64 | }
65 | // set change timeout only when new value computed, hits will not push out the tte, but it is arguable they should not
66 | k[i] = arguments;
67 | return !c || c(i), arguments.val = fn.apply(this, k[i]);
68 | };
69 | else f = function() {
70 | var l = maxargs || arguments.length, kl = k.length, i = -1;
71 | while(++i < kl)if (k[i].length === l) {
72 | var j = -1;
73 | while(++j < l && arguments[j] === k[i][j]); // compare each arg
74 | if (j === l) return k[i].val //the args matched;
75 | ;
76 | }
77 | // set change timeout only when new value computed, hits will not push out the tte, but it is arguable they should not
78 | k[i] = arguments;
79 | return !c || c(i), arguments.val = fn.apply(this, k[i]);
80 | };
81 | // reset all the caches
82 | f.clear = function() {
83 | if (to) clearTimeout(to);
84 | cache.clear();
85 | k = [];
86 | };
87 | f.keys = function() {
88 | return u ? [
89 | ...cache.keys()
90 | ] : k.slice();
91 | };
92 | f.values = function() {
93 | return u ? [
94 | ...cache.values()
95 | ] : k.map((args)=>args.val);
96 | };
97 | return f;
98 | }
99 | var $cf838c15c8b009ba$export$2e2bcd8739ae039 = $cf838c15c8b009ba$export$22f15dd4e5be7e52;
100 |
101 |
102 | export {$cf838c15c8b009ba$export$22f15dd4e5be7e52 as nanomemoize, $cf838c15c8b009ba$export$2e2bcd8739ae039 as default};
103 | //# sourceMappingURL=index.js.map
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nano-memoize",
3 | "version": "3.0.16",
4 | "description": "Faster than fast, smaller than micro ... a nano speed and nano size memoizer.",
5 | "type:": "module",
6 | "source": "src/index.js",
7 | "main": "dist/nano-memoize.js",
8 | "typings": "./index.d.ts",
9 | "module": "./index.js",
10 | "sideEffects": false,
11 | "license": "MIT",
12 | "scripts": {
13 | "test": "npm run build && mocha ./test/index.js",
14 | "prepublish": "npm run build",
15 | "build": "parcel build",
16 | "benchmark": "npm run build && node ./benchmark/index.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/anywhichway/nano-memoize.git"
21 | },
22 | "keywords": [
23 | "memoize",
24 | "moize",
25 | "fast-memoize",
26 | "micro-memoize",
27 | "addy-osmani"
28 | ],
29 | "author": "Simon Y. Blackwell (http://www.github.com/anywhichway)",
30 | "bugs": {
31 | "url": "https://github.com/anywhichway/nano-memoize/issues"
32 | },
33 | "homepage": "https://github.com/anywhichway/nano-memoize#readme",
34 | "devDependencies": {
35 | "benchmark": "^2.1.3",
36 | "blanket": "^1.2.3",
37 | "bread-compressor-cli": "^1.0.5",
38 | "browserify": "^16.5.1",
39 | "chai": "^3.4.1",
40 | "cli-table": "^0.3.11",
41 | "fast-deep-equal": "^3.1.3",
42 | "fast-equals": "^1.6.3",
43 | "fast-memoize": "^2.5.2",
44 | "growl": "^1.10.5",
45 | "hash-it": "^4.0.4",
46 | "iMemoized": "^1.1.8",
47 | "istanbul": "^0.4.2",
48 | "lodash": "^4.17.21",
49 | "lru-memoize": "^1.1.0",
50 | "memize": "^2.1.0",
51 | "memoizee": "^0.4.15",
52 | "memoizerific": "^1.11.3",
53 | "micro-memoize": "^4.1.2",
54 | "mocha": "^10.0.0",
55 | "moize": "^6.1.5",
56 | "ora": "^1.4.0",
57 | "parcel": "^2.9.2",
58 | "ramda": "^0.26.1",
59 | "uglify-es": "^3.3.9",
60 | "underscore": "^1.9.1"
61 | },
62 | "dependencies": {
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2018-2023 Simon Y. Blackwell
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | function vrgs(f) {
26 | var s = f+"",
27 | i = s.indexOf("...");
28 | return i>=0 && (i=0);
29 | }
30 | function nanomemoize(fn,o) {
31 | /*o = {
32 | serializer, // used to serialize arguments of single argument functions, multis are not serialized
33 | equals, // equality tester, will force use of slower multiarg approach even for single arg functions
34 | maxAge, // max cache age is ms, set > 0 && < Infinity if you want automatic clearing
35 | maxArgs, // max args to use for signature
36 | vargs = vrgs(fn) // set to true if function may have variable or beyond-signature arguments, default is best attempt at infering
37 | } = {}
38 | */
39 | o || (o={});
40 | var vargs = o.vargs || vrgs(fn),
41 | k = [], // multiple arg function arg key cache
42 | cache = new Map(), // single arg function key/value cache
43 | u, // flag indicating a unary arg function is in use for clear operation
44 | to, // timeout for clearing cache
45 | d = function(key) { return to = setTimeout(function() {
46 | if(u) {
47 | cache.delete(key);
48 | return;
49 | }
50 | // dealing with multi-arg function, c and k are Arrays
51 | k.splice (key,1);
52 | //v.splice(key,1);
53 | },o.maxAge);
54 | },
55 | c = o.maxAge>0 && o.maxAge args.val); };
107 | return f;
108 | }
109 | export {nanomemoize}
110 |
111 | export default nanomemoize;
112 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | var chai,
2 | expect,
3 | nanomemoize,
4 | lodash;
5 | if(typeof(window)==="undefined") {
6 | chai = require("chai");
7 | expect = chai.expect;
8 | lodash = require("lodash");
9 | nanomemoize = require("../dist/nano-memoize.js").default;
10 | }
11 |
12 | const deepEquals = require('lodash').isEqual;
13 |
14 | const fastDeepEqual = require('fast-deep-equal/es6');
15 |
16 | function singleArg(arg) {
17 | return arg;
18 | }
19 |
20 | function multipleArg(arg1,arg2) {
21 | return {arg1:arg1,arg2:arg2};
22 | }
23 |
24 | singleArg = nanomemoize(singleArg);
25 |
26 | multiplArg = nanomemoize(multipleArg);
27 |
28 | varArg = nanomemoize(function() { return [].slice.call(arguments); });
29 |
30 | describe("Test",function() {
31 | it("memoize functions with function arg", function() {
32 | const memoized = nanomemoize(function (fn) {
33 | return function (o) {
34 | return fn(o);
35 | };
36 | }),
37 | myFunc = memoized(function(o) { return o; }),
38 | result = myFunc(0);
39 | expect(typeof(memoized)).equal("function");
40 | expect(typeof(myFunc)).equal("function");
41 | expect(result).equal(0);
42 | });
43 |
44 | it("single primitive number arg cached",function() {
45 | const value = 1,
46 | result = singleArg(value),
47 | keys = singleArg.keys(),
48 | values = singleArg.values();
49 | expect(result).to.equal(value);
50 | expect(keys[0]).to.equal(value);
51 | expect(values[0]).to.equal(result);
52 | });
53 | it("clear cache",function() {
54 | const value = 1;
55 | singleArg.clear();
56 | expect(singleArg.values()[0]).to.equal(undefined);
57 | expect(singleArg(value)).to.equal(value);
58 | expect(singleArg.values()[0]).to.equal(value);
59 | });
60 | it("single primitive string arg cached",function() {
61 | singleArg.clear();
62 | const value = "1",
63 | result = singleArg(value),
64 | keys = singleArg.keys(),
65 | values = singleArg.values();
66 | expect(keys[0]).to.equal(value);
67 | expect(values[0]).to.equal(value);
68 | });
69 | it("single object arg cached",function() {
70 | const value = {p1:1},
71 | result = singleArg(value);
72 | expect(result).to.equal(value);
73 | });
74 | it("single null arg cached", function() {
75 | const value = null,
76 | result = singleArg(value);
77 | expect(result).to.equal(value);
78 | expect(singleArg(value)).to.equal(value);
79 | });
80 | it("single undefined arg cached", function () {
81 | const res1 = singleArg();
82 | const res2 = singleArg(undefined);
83 | expect(res1).to.equal(res2);
84 | });
85 | it("multiple arg primitive cached",function() {
86 | const result = multipleArg(1,2);
87 | expect(result.arg1).to.equal(1);
88 | expect(result.arg2).to.equal(2);
89 | });
90 | it("multiple arg object cached",function() {
91 | const arg1 = {arg:1},
92 | arg2 = {arg:2},
93 | result = multipleArg(arg1,arg2);
94 | expect(result.arg1.arg).to.equal(1);
95 | expect(result.arg2.arg).to.equal(2);
96 | });
97 | it("multiple arg mixed primitive/object cached",function() {
98 | const arg1 = 1,
99 | arg2 = {arg:2},
100 | result = multipleArg(arg1, arg2);
101 | expect(result.arg1).to.equal(arg1);
102 | expect(result.arg2).to.equal(arg2);
103 | });
104 | it("multiple arg null cached",function() {
105 | const arg1 = null,
106 | arg2 = null,
107 | result = multipleArg(arg1,arg2);
108 | expect(result.arg1).to.equal(arg1);
109 | expect(result.arg2).to.equal(arg2);
110 | });
111 | it("multiple arg works with single",function() {
112 | const arg1 = {arg:1};
113 | result = multipleArg(arg1);
114 | expect(result.arg1.arg).to.equal(1);
115 | });
116 | it("multiple varg mixed length",function() {
117 | const res1 = varArg("multi1", "multi2");
118 | const res2 = varArg("multi1");
119 | expect(res1.length).to.equal(2);
120 | expect(res2.length).to.equal(1);
121 | });
122 | it("zero varg cached", function() {
123 | const res1 = varArg();
124 | const res2 = varArg("multi3");
125 | const res3 = varArg();
126 | expect(res1.length).to.equal(0);
127 | expect(res2.length).to.equal(1);
128 | expect(res3.length).to.equal(0);
129 | });
130 | it("callTimeout",function(done) {
131 | const callTimeout = nanomemoize(function(a,b,cb) { var result = a + b; cb(result); return result; },{maxArgs:2,callTimeout:10});
132 | let result = 0;
133 | const res1 = callTimeout(1,2,(value) => result = value + 1);
134 | expect(res1).to.equal(3);
135 | setTimeout(() => {
136 | expect(result).to.equal(4);
137 | done();
138 | },100)
139 | });
140 | it("maxAge - flush cache",function(done) {
141 | const memoized = nanomemoize((a,b) => a + b,{maxAge: 100})
142 | let keys = memoized.keys(),
143 | values = memoized.values();
144 | expect(keys.length).to.equal(0);
145 | expect(values.length).to.equal(0);
146 | const response = memoized(1,2);
147 | expect(response).to.equal(3);
148 | keys = memoized.keys();
149 | values = memoized.values();
150 | expect(keys.length).to.equal(1);
151 | expect(keys[0][0]).to.equal(1);
152 | expect(keys[0][1]).to.equal(2);
153 | expect(values.length).to.equal(1);
154 | expect(values[0]).to.equal(3);
155 | setTimeout(() => {
156 | let keys = memoized.keys(),
157 | values = memoized.values();
158 | expect(keys.length).to.equal(0); // cache cleared
159 | expect(values.length).to.equal(0); // cache cleared
160 | const response = memoized(1,3);
161 | expect(response).to.equal(4);
162 | keys = memoized.keys();
163 | values = memoized.values();
164 | expect(keys.length).to.equal(1); // new cache value
165 | expect(keys[0][0]).to.equal(1);
166 | expect(keys[0][1]).to.equal(3);
167 | expect(values.length).to.equal(1);
168 | expect(values[0]).to.equal(4);
169 | done();
170 | },1000)
171 | });
172 | it("auto-detect vArg",function() {
173 | const arg1 = 1, arg2 = 2;
174 | expect(Array.isArray(varArg.values())).to.equal(true);
175 | expect(Array.isArray(varArg(arg1,arg2))).to.equal(true);
176 | });
177 | it("expires content single primitive",function(done) {
178 | const expiring = nanomemoize(function(a) { return a; },{maxAge:5});
179 | expect(expiring(1)).to.equal(1);
180 | expect(expiring.values()[0]).to.equal(1);
181 | setTimeout(function() {
182 | expect(expiring.values()[0]).to.equal(undefined);
183 | done();
184 | },20)
185 | });
186 | it("expires content single object",function(done) {
187 | const expiring = nanomemoize(function(a) { return a; },{maxAge:5}),
188 | o = {}
189 | expect(expiring(o)).to.equal(o);
190 | expect(expiring.values()[0]).to.equal(o);
191 | setTimeout(function() {
192 | expect(expiring.values()[0]).to.equal(undefined);
193 | done();
194 | },20)
195 | });
196 | it("expires content multiple",function(done) {
197 | const expiring = nanomemoize(function(a,b) { return {a:a,b:b}; },{maxAge:5}),
198 | result = expiring(1,2);
199 | expect(result.a).to.equal(1);
200 | expect(result.b).to.equal(2);
201 | expect(expiring.values()[0].a).to.equal(1);
202 | expect(expiring.values()[0].b).to.equal(2);
203 | setTimeout(function() {
204 | expect(expiring.values()[0]).to.equal(undefined);
205 | done();
206 | },20)
207 | });
208 | it("optional equal - deepEquals",function() {
209 | const optionalEqual = nanomemoize(function(a,b) { return [a,b]; },{equals:deepEquals}),
210 | [a1,a2] = optionalEqual({a:1}, {a:1}),
211 | values = optionalEqual.values();
212 | expect(deepEquals(a1,a2)).to.equal(true);
213 | expect(values[0].length).to.equal(2);
214 | expect(deepEquals(values[0][0],a1)).to.equal(true);
215 | expect(deepEquals(values[0][1],a2)).to.equal(true);
216 | })
217 | it("optional equal - fastDeepEquals",function() {
218 | const optionalEqual = nanomemoize(function(a,b) { return [a,b]; },{equals:fastDeepEqual}),
219 | [a1,a2] = optionalEqual({a:1}, {a:1}),
220 | values = optionalEqual.values();
221 | expect(fastDeepEqual(a1,a2)).to.equal(true);
222 | expect(values[0].length).to.equal(2);
223 | expect(fastDeepEqual(values[0][0],a1)).to.equal(true);
224 | expect(fastDeepEqual(values[0][1],a2)).to.equal(true);
225 | })
226 | });
--------------------------------------------------------------------------------