UNPKG

9.88 kBMarkdownView Raw
1# memoize-one
2
3A memoization library that only caches the result of the most recent arguments.
4
5> Also [async version](https://github.com/microlinkhq/async-memoize-one).
6
7[![Build Status](https://travis-ci.org/alexreardon/memoize-one.svg?branch=master)](https://travis-ci.org/alexreardon/memoize-one)
8[![npm](https://img.shields.io/npm/v/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
9![types](https://img.shields.io/badge/types-typescript%20%7C%20flow-blueviolet)
10[![dependencies](https://david-dm.org/alexreardon/memoize-one.svg)](https://david-dm.org/alexreardon/memoize-one)
11[![minzip](https://img.shields.io/bundlephobia/minzip/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
12[![Downloads per month](https://img.shields.io/npm/dm/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
13
14## Rationale
15
16Unlike other memoization libraries, `memoize-one` only remembers the latest arguments and result. No need to worry about cache busting mechanisms such as `maxAge`, `maxSize`, `exclusions` and so on which can be prone to memory leaks. `memoize-one` simply remembers the last arguments, and if the function is next called with the same arguments then it returns the previous result.
17
18## Usage
19
20```js
21import { memoizeOne } from 'memoize-one';
22
23const add = (a, b) => a + b;
24const memoizedAdd = memoizeOne(add);
25
26memoizedAdd(1, 2); // 3
27
28memoizedAdd(1, 2); // 3
29// Add function is not executed: previous result is returned
30
31memoizedAdd(2, 3); // 5
32// Add function is called to get new value
33
34memoizedAdd(2, 3); // 5
35// Add function is not executed: previous result is returned
36
37memoizedAdd(1, 2); // 3
38// Add function is called to get new value.
39// While this was previously cached,
40// it is not the latest so the cached result is lost
41```
42
43You can use the default export or a named import
44
45```js
46// Named import
47import { memoizeOne } from 'memoize-one';
48// Default import
49import memoizeOne from 'memoize-one';
50```
51
52## Installation
53
54```bash
55# yarn
56yarn add memoize-one
57
58# npm
59npm install memoize-one --save
60```
61
62## Function argument equality
63
64By default, we apply our own _fast_ and _naive_ equality function to determine whether the arguments provided to your function are equal. You can see the full code here: [are-inputs-equal.ts](https://github.com/alexreardon/memoize-one/blob/master/src/are-inputs-equal.ts).
65
66(By default) function arguments are considered equal if:
67
681. there is same amount of arguments
692. each new argument has strict equality (`===`) with the previous argument
703. **[special case]** if the arguments are not `===` and they are both `NaN` then the argument is treated as equal
71
72What this looks like in practice:
73
74```js
75import { memoizeOne } from 'memoize-one';
76
77// add all numbers provided to the function
78const add = (...args = []) =>
79 args.reduce((current, value) => {
80 return current + value;
81 }, 0);
82const memoizedAdd = memoizeOne(add);
83```
84
85> 1. there is same amount of arguments
86
87```js
88memoizedAdd(1, 2);
89// the amount of arguments has changed, so underlying add function is called
90memoizedAdd(1, 2, 3);
91```
92
93> 2. new arguments have strict equality (`===`) with the previous argument
94
95```js
96memoizedAdd(1, 2);
97// each argument is `===` to the last argument, so cache is used
98memoizedAdd(1, 2);
99// second argument has changed, so add function is called again
100memoizedAdd(1, 3);
101// the first value is not `===` to the previous first value (1 !== 3), so add function is called again
102memoizedAdd(3, 1);
103```
104
105> 3. **[special case]** if the arguments are not `===` and they are both `NaN` then the argument is treated as equal
106
107```js
108memoizedAdd(NaN);
109// Even though NaN !== NaN these arguments are treated as equal
110memoizedAdd(NaN);
111```
112
113## Custom equality function
114
115You can also pass in a custom function for checking the equality of two sets of arguments
116
117```js
118const memoized = memoizeOne(fn, isEqual);
119```
120
121The equality function needs to conform to this `type`:
122
123```ts
124type EqualityFn = (newArgs: any[], lastArgs: any[]) => boolean;
125
126// You can import this type from memoize-one if you like
127
128// typescript
129import { EqualityFn } from 'memoize-one';
130
131// flow
132import type { EqualityFn } from 'memoize-one';
133```
134
135An equality function should return `true` if the arguments are equal. If `true` is returned then the wrapped function will not be called.
136
137A custom equality function needs to compare `Arrays`. The `newArgs` array will be a new reference every time so a simple `newArgs === lastArgs` will always return `false`.
138
139Equality functions are not called if the `this` context of the function has changed (see below).
140
141Here is an example that uses a [dequal](https://github.com/lukeed/dequal) deep equal equality check
142
143> `dequal` correctly handles deep comparing two arrays
144
145```js
146import memoizeOne from 'memoize-one';
147import { dequal as isDeepEqual } from 'dequal';
148
149const identity = (x) => x;
150
151const shallowMemoized = memoizeOne(identity);
152const deepMemoized = memoizeOne(identity, isDeepEqual);
153
154const result1 = shallowMemoized({ foo: 'bar' });
155const result2 = shallowMemoized({ foo: 'bar' });
156
157result1 === result2; // false - difference reference
158
159const result3 = deepMemoized({ foo: 'bar' });
160const result4 = deepMemoized({ foo: 'bar' });
161
162result3 === result4; // true - arguments are deep equal
163```
164
165## `this`
166
167### `memoize-one` correctly respects `this` control
168
169This library takes special care to maintain, and allow control over the the `this` context for **both** the original function being memoized as well as the returned memoized function. Both the original function and the memoized function's `this` context respect [all the `this` controlling techniques](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md):
170
171- new bindings (`new`)
172- explicit binding (`call`, `apply`, `bind`);
173- implicit binding (call site: `obj.foo()`);
174- default binding (`window` or `undefined` in `strict mode`);
175- fat arrow binding (binding to lexical `this`)
176- ignored this (pass `null` as `this` to explicit binding)
177
178### Changes to `this` is considered an argument change
179
180Changes to the running context (`this`) of a function can result in the function returning a different value even though its arguments have stayed the same:
181
182```js
183function getA() {
184 return this.a;
185}
186
187const temp1 = {
188 a: 20,
189};
190const temp2 = {
191 a: 30,
192};
193
194getA.call(temp1); // 20
195getA.call(temp2); // 30
196```
197
198Therefore, in order to prevent against unexpected results, `memoize-one` takes into account the current execution context (`this`) of the memoized function. If `this` is different to the previous invocation then it is considered a change in argument. [further discussion](https://github.com/alexreardon/memoize-one/issues/3).
199
200Generally this will be of no impact if you are not explicity controlling the `this` context of functions you want to memoize with [explicit binding](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#explicit-binding) or [implicit binding](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#implicit-binding). `memoize-One` will detect when you are manipulating `this` and will then consider the `this` context as an argument. If `this` changes, it will re-execute the original function even if the arguments have not changed.
201
202## When your result function `throw`s
203
204> There is no caching when your result function throws
205
206If your result function `throw`s then the memoized function will also throw. The throw will not break the memoized functions existing argument cache. It means the memoized function will pretend like it was never called with arguments that made it `throw`.
207
208```js
209const canThrow = (name: string) => {
210 console.log('called');
211 if (name === 'throw') {
212 throw new Error(name);
213 }
214 return { name };
215};
216
217const memoized = memoizeOne(canThrow);
218
219const value1 = memoized('Alex');
220// console.log => 'called'
221const value2 = memoized('Alex');
222// result function not called
223
224console.log(value1 === value2);
225// console.log => true
226
227try {
228 memoized('throw');
229 // console.log => 'called'
230} catch (e) {
231 firstError = e;
232}
233
234try {
235 memoized('throw');
236 // console.log => 'called'
237 // the result function was called again even though it was called twice
238 // with the 'throw' string
239} catch (e) {
240 secondError = e;
241}
242
243console.log(firstError !== secondError);
244
245const value3 = memoized('Alex');
246// result function not called as the original memoization cache has not been busted
247console.log(value1 === value3);
248// console.log => true
249```
250
251## Performance 🚀
252
253### Tiny
254
255`memoize-one` is super lightweight at [![min](https://img.shields.io/bundlephobia/min/memoize-one.svg?label=)](https://www.npmjs.com/package/memoize-one) minified and [![minzip](https://img.shields.io/bundlephobia/minzip/memoize-one.svg?label=)](https://www.npmjs.com/package/memoize-one) gzipped. (`1KB` = `1,024 Bytes`)
256
257### Extremely fast
258
259`memoize-one` performs better or on par with than other popular memoization libraries for the purpose of remembering the latest invocation.
260
261**Results**
262
263- [simple arguments](https://www.measurethat.net/Benchmarks/ShowResult/4452)
264- [complex arguments](https://www.measurethat.net/Benchmarks/ShowResult/4488)
265
266The comparisons are not exhaustive and are primarily to show that `memoize-one` accomplishes remembering the latest invocation really fast. The benchmarks do not take into account the differences in feature sets, library sizes, parse time, and so on.
267
268## Code health 👍
269
270- Tested with all built in [JavaScript types](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/types%20%26%20grammar/ch1.md).
271- 100% code coverage
272- [Continuous integration](https://travis-ci.org/alexreardon/memoize-one) to run tests and type checks.
273- Written in `Typescript`
274- Correct typing for `Typescript` and `flow` type systems
275- No dependencies