├── .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 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/30ce201484754fa5b0a6c6046abb842d)](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://img.shields.io/npm/v/nano-memoize.svg?style=flat)](https://www.npmjs.org/package/nano-memoize) [![](https://img.shields.io/npm/dm/nano-memoize.svg)](https://www.npmjs.org/package/nano-memoize) [![](https://img.shields.io/bundlephobia/minzip/nano-memoize.svg)](https://bundlephobia.com/package/nano-memoize) [![](https://packagephobia.com/badge?p=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 | }); --------------------------------------------------------------------------------