UNPKG

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