UNPKG

8.1 kBMarkdownView Raw
1# mem
2
3> [Memoize](https://en.wikipedia.org/wiki/Memoization) functions - An optimization used to speed up consecutive function calls by caching the result of calls with identical input
4
5Memory is automatically released when an item expires or the cache is cleared.
6
7By default, **only the first argument is considered** and it only works with [primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). If you need to cache multiple arguments or cache `object`s *by value*, have a look at alternative [caching strategies](#caching-strategy) below.
8
9## Install
10
11```
12$ npm install mem
13```
14
15## Usage
16
17```js
18import mem from 'mem';
19
20let index = 0;
21const counter = () => ++index;
22const memoized = mem(counter);
23
24memoized('foo');
25//=> 1
26
27// Cached as it's the same argument
28memoized('foo');
29//=> 1
30
31// Not cached anymore as the argument changed
32memoized('bar');
33//=> 2
34
35memoized('bar');
36//=> 2
37
38// Only the first argument is considered by default
39memoized('bar', 'foo');
40//=> 2
41```
42
43##### Works fine with promise returning functions
44
45```js
46import mem from 'mem';
47
48let index = 0;
49const counter = async () => ++index;
50const memoized = mem(counter);
51
52console.log(await memoized());
53//=> 1
54
55// The return value didn't increase as it's cached
56console.log(await memoized());
57//=> 1
58```
59
60```js
61import mem from 'mem';
62import got from 'got';
63import delay from 'delay';
64
65const memGot = mem(got, {maxAge: 1000});
66
67await memGot('https://sindresorhus.com');
68
69// This call is cached
70await memGot('https://sindresorhus.com');
71
72await delay(2000);
73
74// This call is not cached as the cache has expired
75await memGot('https://sindresorhus.com');
76```
77
78### Caching strategy
79
80By default, only the first argument is compared via exact equality (`===`) to determine whether a call is identical.
81
82```js
83const power = mem((a, b) => Math.power(a, b));
84
85power(2, 2); // => 4, stored in cache with the key 2 (number)
86power(2, 3); // => 4, retrieved from cache at key 2 (number), it's wrong
87```
88
89You will have to use the `cache` and `cacheKey` options appropriate to your function. In this specific case, the following could work:
90
91```js
92const power = mem((a, b) => Math.power(a, b), {
93 cacheKey: arguments_ => arguments_.join(',')
94});
95
96power(2, 2); // => 4, stored in cache with the key '2,2' (both arguments as one string)
97power(2, 3); // => 8, stored in cache with the key '2,3'
98```
99
100More advanced examples follow.
101
102#### Example: Options-like argument
103
104If your function accepts an object, it won't be memoized out of the box:
105
106```js
107const heavyMemoizedOperation = mem(heavyOperation);
108
109heavyMemoizedOperation({full: true}); // Stored in cache with the object as key
110heavyMemoizedOperation({full: true}); // Stored in cache with the object as key, again
111// The objects look the same but for JS they're two different objects
112```
113
114You might want to serialize or hash them, for example using `JSON.stringify` or something like [serialize-javascript](https://github.com/yahoo/serialize-javascript), which can also serialize `RegExp`, `Date` and so on.
115
116```js
117const heavyMemoizedOperation = mem(heavyOperation, {cacheKey: JSON.stringify});
118
119heavyMemoizedOperation({full: true}); // Stored in cache with the key '[{"full":true}]' (string)
120heavyMemoizedOperation({full: true}); // Retrieved from cache
121```
122
123The same solution also works if it accepts multiple serializable objects:
124
125```js
126const heavyMemoizedOperation = mem(heavyOperation, {cacheKey: JSON.stringify});
127
128heavyMemoizedOperation('hello', {full: true}); // Stored in cache with the key '["hello",{"full":true}]' (string)
129heavyMemoizedOperation('hello', {full: true}); // Retrieved from cache
130```
131
132#### Example: Multiple non-serializable arguments
133
134If your function accepts multiple arguments that aren't supported by `JSON.stringify` (e.g. DOM elements and functions), you can instead extend the initial exact equality (`===`) to work on multiple arguments using [`many-keys-map`](https://github.com/fregante/many-keys-map):
135
136```js
137import ManyKeysMap from 'many-keys-map';
138
139const addListener = (emitter, eventName, listener) => emitter.on(eventName, listener);
140
141const addOneListener = mem(addListener, {
142 cacheKey: arguments_ => arguments_, // Use *all* the arguments as key
143 cache: new ManyKeysMap() // Correctly handles all the arguments for exact equality
144});
145
146addOneListener(header, 'click', console.log); // `addListener` is run, and it's cached with the `arguments` array as key
147addOneListener(header, 'click', console.log); // `addListener` is not run again
148addOneListener(mainContent, 'load', console.log); // `addListener` is run, and it's cached with the `arguments` array as key
149```
150
151Better yet, if your function’s arguments are compatible with `WeakMap`, you should use [`deep-weak-map`](https://github.com/futpib/deep-weak-map) instead of `many-keys-map`. This will help avoid memory leaks.
152
153## API
154
155### mem(fn, options?)
156
157#### fn
158
159Type: `Function`
160
161Function to be memoized.
162
163#### options
164
165Type: `object`
166
167##### maxAge
168
169Type: `number`\
170Default: `Infinity`
171
172Milliseconds until the cache expires.
173
174##### cacheKey
175
176Type: `Function`\
177Default: `arguments_ => arguments_[0]`\
178Example: `arguments_ => JSON.stringify(arguments_)`
179
180Determines the cache key for storing the result based on the function arguments. By default, **only the first argument is considered**.
181
182A `cacheKey` function can return any type supported by `Map` (or whatever structure you use in the `cache` option).
183
184Refer to the [caching strategies](#caching-strategy) section for more information.
185
186##### cache
187
188Type: `object`\
189Default: `new Map()`
190
191Use a different cache storage. Must implement the following methods: `.has(key)`, `.get(key)`, `.set(key, value)`, `.delete(key)`, and optionally `.clear()`. You could for example use a `WeakMap` instead or [`quick-lru`](https://github.com/sindresorhus/quick-lru) for a LRU cache.
192
193Refer to the [caching strategies](#caching-strategy) section for more information.
194
195### memDecorator(options)
196
197Returns a [decorator](https://github.com/tc39/proposal-decorators) to memoize class methods or static class methods.
198
199Notes:
200
201- Only class methods and getters/setters can be memoized, not regular functions (they aren't part of the proposal);
202- Only [TypeScript’s decorators](https://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators) are supported, not [Babel’s](https://babeljs.io/docs/en/babel-plugin-proposal-decorators), which use a different version of the proposal;
203- Being an experimental feature, they need to be enabled with `--experimentalDecorators`; follow TypeScript’s docs.
204
205#### options
206
207Type: `object`
208
209Same as options for `mem()`.
210
211```ts
212import {memDecorator} from 'mem';
213
214class Example {
215 index = 0
216
217 @memDecorator()
218 counter() {
219 return ++this.index;
220 }
221}
222
223class ExampleWithOptions {
224 index = 0
225
226 @memDecorator({maxAge: 1000})
227 counter() {
228 return ++this.index;
229 }
230}
231```
232
233### memClear(fn)
234
235Clear all cached data of a memoized function.
236
237#### fn
238
239Type: `Function`
240
241Memoized function.
242
243## Tips
244
245### Cache statistics
246
247If you want to know how many times your cache had a hit or a miss, you can make use of [stats-map](https://github.com/SamVerschueren/stats-map) as a replacement for the default cache.
248
249#### Example
250
251```js
252import mem from 'mem';
253import StatsMap from 'stats-map';
254import got from 'got';
255
256const cache = new StatsMap();
257const memGot = mem(got, {cache});
258
259await memGot('https://sindresorhus.com');
260await memGot('https://sindresorhus.com');
261await memGot('https://sindresorhus.com');
262
263console.log(cache.stats);
264//=> {hits: 2, misses: 1}
265```
266
267## Related
268
269- [p-memoize](https://github.com/sindresorhus/p-memoize) - Memoize promise-returning & async functions
270
271---
272
273<div align="center">
274 <b>
275 <a href="https://tidelift.com/subscription/pkg/npm-mem?utm_source=npm-mem&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
276 </b>
277 <br>
278 <sub>
279 Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
280 </sub>
281</div>