Skip to content

Commit

Permalink
Rename package from mem to memoize
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Nov 14, 2023
1 parent 8f207cf commit 4756892
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 87 deletions.
28 changes: 14 additions & 14 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ export type Options<
You can have it cache **all** the arguments by value with `JSON.stringify`, if they are compatible:
```
import mem from 'mem';
import memoize from 'memoize';
mem(function_, {cacheKey: JSON.stringify});
memoize(function_, {cacheKey: JSON.stringify});
```
Or you can use a more full-featured serializer like [serialize-javascript](https://github.com/yahoo/serialize-javascript) to add support for `RegExp`, `Date` and so on.
```
import mem from 'mem';
import memoize from 'memoize';
import serializeJavascript from 'serialize-javascript';
mem(function_, {cacheKey: serializeJavascript});
memoize(function_, {cacheKey: serializeJavascript});
```
@default arguments_ => arguments_[0]
Expand All @@ -72,11 +72,11 @@ export type Options<
@example
```
import mem from 'mem';
import memoize from 'memoize';
let index = 0;
const counter = () => ++index;
const memoized = mem(counter);
const memoized = memoize(counter);
memoized('foo');
//=> 1
Expand All @@ -93,7 +93,7 @@ memoized('bar');
//=> 2
```
*/
export default function mem<
export default function memoize<
FunctionToMemoize extends AnyFunction,
CacheKeyType,
>(
Expand Down Expand Up @@ -163,12 +163,12 @@ export default function mem<
@example
```
import {memDecorator} from 'mem';
import {memoizeDecorator} from 'memoize';
class Example {
index = 0
@memDecorator()
@memoizeDecorator()
counter() {
return ++this.index;
}
Expand All @@ -177,14 +177,14 @@ class Example {
class ExampleWithOptions {
index = 0
@memDecorator({maxAge: 1000})
@memoizeDecorator({maxAge: 1000})
counter() {
return ++this.index;
}
}
```
*/
export function memDecorator<
export function memoizeDecorator<
FunctionToMemoize extends AnyFunction,
CacheKeyType,
>(
Expand All @@ -208,7 +208,7 @@ export function memDecorator<

descriptor.get = function () {
if (!instanceMap.has(this)) {
const value = mem(input, options) as FunctionToMemoize;
const value = memoize(input, options) as FunctionToMemoize;
instanceMap.set(this, value);
return value;
}
Expand All @@ -223,7 +223,7 @@ Clear all cached data of a memoized function.
@param fn - The memoized function.
*/
export function memClear(fn: AnyFunction): void {
export function memoizeClear(fn: AnyFunction): void {
const cache = cacheStore.get(fn);
if (!cache) {
throw new TypeError('Can\'t clear a function that was not memoized!');
Expand All @@ -233,7 +233,7 @@ export function memClear(fn: AnyFunction): void {
throw new TypeError('The cache Map can\'t be cleared!');
}

cache.clear?.();
cache.clear();

for (const timer of cacheTimerStore.get(fn) ?? []) {
clearTimeout(timer);
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "mem",
"name": "memoize",
"version": "9.0.2",
"description": "Memoize functions - An optimization used to speed up consecutive function calls by caching the result of calls with identical input",
"license": "MIT",
"repository": "sindresorhus/mem",
"funding": "https://github.com/sindresorhus/mem?sponsor=1",
"repository": "sindresorhus/memoize",
"funding": "https://github.com/sindresorhus/memoize?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
Expand Down
67 changes: 39 additions & 28 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# mem
# memoize

> [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
Expand All @@ -13,17 +13,17 @@ If you want to memoize Promise-returning functions (like `async` functions), you
## Install

```sh
npm install mem
npm install memoize
```

## Usage

```js
import mem from 'mem';
import memoize from 'memoize';

let index = 0;
const counter = () => ++index;
const memoized = mem(counter);
const memoized = memoize(counter);

memoized('foo');
//=> 1
Expand All @@ -49,11 +49,11 @@ memoized('bar', 'foo');
But you might want to use [p-memoize](https://github.com/sindresorhus/p-memoize) for more Promise-specific behaviors.

```js
import mem from 'mem';
import memoize from 'memoize';

let index = 0;
const counter = async () => ++index;
const memoized = mem(counter);
const memoized = memoize(counter);

console.log(await memoized());
//=> 1
Expand All @@ -64,29 +64,31 @@ console.log(await memoized());
```

```js
import mem from 'mem';
import memoize from 'memoize';
import got from 'got';
import delay from 'delay';

const memGot = mem(got, {maxAge: 1000});
const memoizedGot = memoize(got, {maxAge: 1000});

await memGot('https://sindresorhus.com');
await memoizedGot('https://sindresorhus.com');

// This call is cached
await memGot('https://sindresorhus.com');
await memoizedGot('https://sindresorhus.com');

await delay(2000);

// This call is not cached as the cache has expired
await memGot('https://sindresorhus.com');
await memoizedGot('https://sindresorhus.com');
```

### Caching strategy

By default, only the first argument is compared via exact equality (`===`) to determine whether a call is identical.

```js
const power = mem((a, b) => Math.power(a, b));
import memoize from 'memoize';

const power = memoize((a, b) => Math.power(a, b));

power(2, 2); // => 4, stored in cache with the key 2 (number)
power(2, 3); // => 4, retrieved from cache at key 2 (number), it's wrong
Expand All @@ -95,7 +97,9 @@ power(2, 3); // => 4, retrieved from cache at key 2 (number), it's wrong
You will have to use the `cache` and `cacheKey` options appropriate to your function. In this specific case, the following could work:

```js
const power = mem((a, b) => Math.power(a, b), {
import memoize from 'memoize';

const power = memoize((a, b) => Math.power(a, b), {
cacheKey: arguments_ => arguments_.join(',')
});

Expand All @@ -110,7 +114,9 @@ More advanced examples follow.
If your function accepts an object, it won't be memoized out of the box:

```js
const heavyMemoizedOperation = mem(heavyOperation);
import memoize from 'memoize';

const heavyMemoizedOperation = memoize(heavyOperation);

heavyMemoizedOperation({full: true}); // Stored in cache with the object as key
heavyMemoizedOperation({full: true}); // Stored in cache with the object as key, again
Expand All @@ -120,7 +126,9 @@ heavyMemoizedOperation({full: true}); // Stored in cache with the object as key,
You 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.

```js
const heavyMemoizedOperation = mem(heavyOperation, {cacheKey: JSON.stringify});
import memoize from 'memoize';

const heavyMemoizedOperation = memoize(heavyOperation, {cacheKey: JSON.stringify});

heavyMemoizedOperation({full: true}); // Stored in cache with the key '[{"full":true}]' (string)
heavyMemoizedOperation({full: true}); // Retrieved from cache
Expand All @@ -129,7 +137,9 @@ heavyMemoizedOperation({full: true}); // Retrieved from cache
The same solution also works if it accepts multiple serializable objects:

```js
const heavyMemoizedOperation = mem(heavyOperation, {cacheKey: JSON.stringify});
import memoize from 'memoize';

const heavyMemoizedOperation = memoize(heavyOperation, {cacheKey: JSON.stringify});

heavyMemoizedOperation('hello', {full: true}); // Stored in cache with the key '["hello",{"full":true}]' (string)
heavyMemoizedOperation('hello', {full: true}); // Retrieved from cache
Expand All @@ -140,11 +150,12 @@ heavyMemoizedOperation('hello', {full: true}); // Retrieved from cache
If 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):

```js
import memoize from 'memoize';
import ManyKeysMap from 'many-keys-map';

const addListener = (emitter, eventName, listener) => emitter.on(eventName, listener);

const addOneListener = mem(addListener, {
const addOneListener = memoize(addListener, {
cacheKey: arguments_ => arguments_, // Use *all* the arguments as key
cache: new ManyKeysMap() // Correctly handles all the arguments for exact equality
});
Expand All @@ -158,7 +169,7 @@ Better yet, if your function’s arguments are compatible with `WeakMap`, you sh

## API

### mem(fn, options?)
### memoize(fn, options?)

#### fn

Expand Down Expand Up @@ -212,15 +223,15 @@ Notes:

Type: `object`

Same as options for `mem()`.
Same as options for `memoize()`.

```ts
import {memDecorator} from 'mem';
import {memoizeDecorator} from 'memoize';

class Example {
index = 0

@memDecorator()
@memoizeDecorator()
counter() {
return ++this.index;
}
Expand All @@ -229,14 +240,14 @@ class Example {
class ExampleWithOptions {
index = 0

@memDecorator({maxAge: 1000})
@memoizeDecorator({maxAge: 1000})
counter() {
return ++this.index;
}
}
```

### memClear(fn)
### memoizeClear(fn)

Clear all cached data of a memoized function.

Expand All @@ -255,16 +266,16 @@ If you want to know how many times your cache had a hit or a miss, you can make
#### Example

```js
import mem from 'mem';
import memoize from 'memoize';
import StatsMap from 'stats-map';
import got from 'got';

const cache = new StatsMap();
const memGot = mem(got, {cache});
const memoizedGot = memoize(got, {cache});

await memGot('https://sindresorhus.com');
await memGot('https://sindresorhus.com');
await memGot('https://sindresorhus.com');
await memoizedGot('https://sindresorhus.com');
await memoizedGot('https://sindresorhus.com');
await memoizedGot('https://sindresorhus.com');

console.log(cache.stats);
//=> {hits: 2, misses: 1}
Expand Down
26 changes: 13 additions & 13 deletions test-d/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import {expectType} from 'tsd';
import mem, {memClear} from '../index.js';
import memoize, {memoizeClear} from '../index.js';

// eslint-disable-next-line unicorn/prefer-native-coercion-functions -- Required `string` type
const fn = (text: string) => Boolean(text);

expectType<typeof fn>(mem(fn));
expectType<typeof fn>(mem(fn, {maxAge: 1}));
expectType<typeof fn>(mem(fn, {cacheKey: ([firstArgument]: [string]) => firstArgument}));
expectType<typeof fn>(memoize(fn));
expectType<typeof fn>(memoize(fn, {maxAge: 1}));
expectType<typeof fn>(memoize(fn, {cacheKey: ([firstArgument]: [string]) => firstArgument}));
expectType<typeof fn>(
mem(fn, {
memoize(fn, {
// The cacheKey returns an array. This isn't deduplicated by a regular Map, but it's valid. The correct solution would be to use ManyKeysMap to deduplicate it correctly
cacheKey: (arguments_: [string]) => arguments_,
cache: new Map<[string], {data: boolean; maxAge: number}>(),
}),
);
expectType<typeof fn>(
// The `firstArgument` of `fn` is of type `string`, so it's used
mem(fn, {cache: new Map<string, {data: boolean; maxAge: number}>()}),
memoize(fn, {cache: new Map<string, {data: boolean; maxAge: number}>()}),
);

/* Overloaded function tests */
Expand All @@ -26,29 +26,29 @@ function overloadedFn(parameter: boolean): boolean {
return parameter;
}

expectType<typeof overloadedFn>(mem(overloadedFn));
expectType<true>(mem(overloadedFn)(true));
expectType<false>(mem(overloadedFn)(false));
expectType<typeof overloadedFn>(memoize(overloadedFn));
expectType<true>(memoize(overloadedFn)(true));
expectType<false>(memoize(overloadedFn)(false));

memClear(fn);
memoizeClear(fn);

// `cacheKey` tests.
// The argument should match the memoized function’s parameters
// eslint-disable-next-line unicorn/prefer-native-coercion-functions -- Required `string` type
mem((text: string) => Boolean(text), {
memoize((text: string) => Boolean(text), {
cacheKey(arguments_) {
expectType<[string]>(arguments_);
},
});

mem(() => 1, {
memoize(() => 1, {
cacheKey(arguments_) {
expectType<[]>(arguments_); // eslint-disable-line @typescript-eslint/ban-types
},
});

// Ensures that the various cache functions infer their arguments type from the return type of `cacheKey`
mem((_arguments: {key: string}) => 1, {
memoize((_arguments: {key: string}) => 1, {
cacheKey(arguments_: [{key: string}]) {
expectType<[{key: string}]>(arguments_);
return new Date();
Expand Down

0 comments on commit 4756892

Please sign in to comment.