UNPKG

8.75 kBMarkdownView Raw
1# memoize-one
2
3A memoization library that only caches the result of the most recent arguments.
4
5[![Build Status](https://travis-ci.org/alexreardon/memoize-one.svg?branch=master)](https://travis-ci.org/alexreardon/memoize-one)
6[![npm](https://img.shields.io/npm/v/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
7[![dependencies](https://david-dm.org/alexreardon/memoize-one.svg)](https://david-dm.org/alexreardon/memoize-one)
8[![Downloads per month](https://img.shields.io/npm/dm/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
9[![min](https://img.shields.io/bundlephobia/min/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
10[![minzip](https://img.shields.io/bundlephobia/minzip/memoize-one.svg)](https://www.npmjs.com/package/memoize-one)
11
12## Rationale
13
14Unlike 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.
15
16## Usage
17
18### Standard 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
43## Installation
44
45```bash
46# yarn
47yarn add memoize-one
48
49# npm
50npm install memoize-one --save
51```
52
53## Module usage
54
55### ES6 module
56
57```js
58import memoizeOne from 'memoize-one';
59```
60
61### CommonJS
62
63If you are in a CommonJS environment (eg [Node](https://nodejs.org)), then **you will need to add `.default` to your import**:
64
65```js
66const memoizeOne = require('memoize-one').default;
67```
68
69## Custom equality function
70
71You can also pass in a custom function for checking the equality of two sets of arguments
72
73```js
74const memoized = memoizeOne(fn, isEqual);
75type EqualityFn = (newArgs: mixed[], oldArgs: mixed[]) => boolean;
76```
77
78An equality function should return `true` if the arguments are equal. If `true` is returned then the wrapped function will not be called.
79
80The default equality function is a shallow equal check of all arguments (each argument is compared with `===`). The default equality function also does not check anything if the length of the arguments changes. You are welcome to decide if you want to return `false` if the `length` of the arguments is not equal
81
82```js
83const simpleIsEqual: EqualityFn = (
84 newArgs: mixed[],
85 lastArgs: mixed[],
86): boolean =>
87 newArgs.length === lastArgs.length &&
88 newArgs.every(
89 (newArg: mixed, index: number): boolean =>
90 shallowEqual(newArg, lastArgs[index]),
91 );
92```
93
94A 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`.
95
96Equality functions are not called if the `this` context of the function has changed (see below).
97
98Here is an example that uses a `lodash.isequal` deep equal equality check
99
100> `lodash.isequal` correctly handles deep comparing two arrays
101
102```js
103import memoizeOne from 'memoize-one';
104import isDeepEqual from 'lodash.isequal';
105
106const identity = x => x;
107
108const shallowMemoized = memoizeOne(identity);
109const deepMemoized = memoizeOne(identity, isDeepEqual);
110
111const result1 = shallowMemoized({ foo: 'bar' });
112const result2 = shallowMemoized({ foo: 'bar' });
113
114result1 === result2; // false - difference reference
115
116const result3 = deepMemoized({ foo: 'bar' });
117const result4 = deepMemoized({ foo: 'bar' });
118
119result3 === result4; // true - arguments are deep equal
120```
121
122## `this`
123
124### `memoize-one` correctly respects `this` control
125
126This 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):
127
128- new bindings (`new`)
129- explicit binding (`call`, `apply`, `bind`);
130- implicit binding (call site: `obj.foo()`);
131- default binding (`window` or `undefined` in `strict mode`);
132- fat arrow binding (binding to lexical `this`)
133- ignored this (pass `null` as `this` to explicit binding)
134
135### Changes to `this` is considered an argument change
136
137Changes 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:
138
139```js
140function getA() {
141 return this.a;
142}
143
144const temp1 = {
145 a: 20,
146};
147const temp2 = {
148 a: 30,
149};
150
151getA.call(temp1); // 20
152getA.call(temp2); // 30
153```
154
155Therefore, 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).
156
157Generally 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.
158
159## When your result function `throw`s
160
161> There is no caching when your result function throws
162
163If 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`.
164
165```js
166const canThrow = (name: string) => {
167 console.log('called');
168 if (name === 'throw') {
169 throw new Error(name);
170 }
171 return { name };
172};
173
174const memoized = memoizeOne(canThrow);
175
176const value1 = memoized('Alex');
177// console.log => 'called'
178const value2 = memoized('Alex');
179// result function not called
180
181console.log(value1 === value2);
182// console.log => true
183
184try {
185 memoized('throw');
186 // console.log => 'called'
187} catch (e) {
188 firstError = e;
189}
190
191try {
192 memoized('throw');
193 // console.log => 'called'
194 // the result function was called again even though it was called twice
195 // with the 'throw' string
196} catch (e) {
197 secondError = e;
198}
199
200console.log(firstError !== secondError);
201
202const value3 = memoized('Alex');
203// result function not called as the original memoization cache has not been busted
204console.log(value1 === value3);
205// console.log => true
206```
207
208## Performance :rocket:
209
210### Tiny
211
212`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`)
213
214### Extremely fast
215
216`memoize-one` performs better or on par with than other popular memoization libraries for the purpose of remembering the latest invocation.
217
218**Results**
219
220- [simple arguments](https://www.measurethat.net/Benchmarks/ShowResult/4452)
221- [complex arguments](https://www.measurethat.net/Benchmarks/ShowResult/4488)
222
223The 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.
224
225## Code health :thumbsup:
226
227- Tested with all built in [JavaScript types](https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch1.md).
228- 100% code coverage
229- [Continuous integration](https://travis-ci.org/alexreardon/memoize-one) to run tests and type checks.
230- [`Flow` types](http://flowtype.org) for safer internal execution and external consumption. Also allows for editor autocompletion.
231- Follows [Semantic versioning (2.0)](http://semver.org/) for safer consumption.
232- No dependencies