From 22c4b5e8fb033532eb070e24b26521e7898dfd77 Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Sun, 7 May 2023 04:14:02 +0800 Subject: [PATCH 1/5] Update dependencies (#91) --- .github/funding.yml | 2 +- .github/workflows/main.yml | 8 ++--- index.ts | 16 +++++----- package.json | 28 ++++++++---------- test-d/index.test-d.ts | 18 +++++++----- test.ts | 60 +++++--------------------------------- tsconfig.json | 5 +++- 7 files changed, 47 insertions(+), 90 deletions(-) diff --git a/.github/funding.yml b/.github/funding.yml index 10ed145..5919969 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1,2 +1,2 @@ -github: [sindresorhus, bfred-it] +github: [sindresorhus, fregante] tidelift: npm/mem diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b8aa86..ed4040b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,12 +10,12 @@ jobs: fail-fast: false matrix: node-version: + #- 20 + - 18 - 16 - - 14 - - 12 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/index.ts b/index.ts index f1b4366..6062813 100644 --- a/index.ts +++ b/index.ts @@ -5,23 +5,23 @@ type AnyFunction = (...arguments_: any) => any; const cacheStore = new WeakMap>(); -interface CacheStorageContent { +type CacheStorageContent = { data: ValueType; maxAge: number; -} +}; -interface CacheStorage { +type CacheStorage = { has: (key: KeyType) => boolean; get: (key: KeyType) => CacheStorageContent | undefined; set: (key: KeyType, value: CacheStorageContent) => void; delete: (key: KeyType) => void; clear?: () => void; -} +}; -export interface Options< +export type Options< FunctionToMemoize extends AnyFunction, CacheKeyType, -> { +> = { /** Milliseconds until the cache expires. @@ -63,7 +63,7 @@ export interface Options< @example new WeakMap() */ readonly cache?: CacheStorage>; -} +}; /** [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. @@ -174,7 +174,7 @@ export function memDecorator< propertyKey: string, descriptor: PropertyDescriptor, ): void => { - const input = target[propertyKey]; // eslint-disable-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const input = target[propertyKey]; // eslint-disable-line @typescript-eslint/no-unsafe-assignment if (typeof input !== 'function') { throw new TypeError('The decorated value must be a function'); diff --git a/package.json b/package.json index be83c59..5b70288 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "type": "module", "exports": "./dist/index.js", "engines": { - "node": ">=12.20" + "node": ">=16" }, "scripts": { "test": "xo && ava && npm run build && tsd", @@ -38,30 +38,26 @@ "promise" ], "dependencies": { - "map-age-cleaner": "^0.1.3", + "map-age-cleaner": "^0.2.0", "mimic-fn": "^4.0.0" }, "devDependencies": { - "@ava/typescript": "^1.1.1", - "@sindresorhus/tsconfig": "^1.0.2", - "@types/serialize-javascript": "^4.0.0", - "ava": "^3.15.0", - "del-cli": "^3.0.1", - "delay": "^4.4.0", - "serialize-javascript": "^5.0.1", - "ts-node": "^10.1.0", - "tsd": "^0.13.1", - "typescript": "^4.3.5", - "xo": "^0.41.0" + "@sindresorhus/tsconfig": "^3.0.1", + "@types/serialize-javascript": "^5.0.2", + "ava": "^5.2.0", + "del-cli": "^5.0.0", + "delay": "^5.0.0", + "serialize-javascript": "^6.0.1", + "ts-node": "^10.9.1", + "tsd": "^0.28.1", + "typescript": "^5.0.4", + "xo": "^0.54.2" }, "ava": { "timeout": "1m", "extensions": { "ts": "module" }, - "nonSemVerExperiments": { - "configurableModuleFormat": true - }, "nodeArguments": [ "--loader=ts-node/esm" ] diff --git a/test-d/index.test-d.ts b/test-d/index.test-d.ts index e1b6f19..c698ce5 100644 --- a/test-d/index.test-d.ts +++ b/test-d/index.test-d.ts @@ -1,6 +1,7 @@ import {expectType} from 'tsd'; -import mem, {memClear} from '..'; +import mem, {memClear} from '../index.js'; +// eslint-disable-next-line unicorn/prefer-native-coercion-functions -- Required `string` type const fn = (text: string) => Boolean(text); expectType(mem(fn)); @@ -33,26 +34,27 @@ memClear(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), { - cacheKey: arguments_ => { + cacheKey(arguments_) { expectType<[string]>(arguments_); }, }); mem(() => 1, { - cacheKey: arguments_ => { + 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, { - cacheKey: (arguments_: [{key: string}]) => { + cacheKey(arguments_: [{key: string}]) { expectType<[{key: string}]>(arguments_); return new Date(); }, cache: { - get: key => { + get(key) { expectType(key); return { @@ -60,15 +62,15 @@ mem((_arguments: {key: string}) => 1, { maxAge: 2, }; }, - set: (key, data) => { + set(key, data) { expectType(key); expectType<{data: number; maxAge: number}>(data); }, - has: key => { + has(key) { expectType(key); return true; }, - delete: key => { + delete(key) { expectType(key); }, clear: () => undefined, diff --git a/test.ts b/test.ts index 23c68a8..41bf19a 100644 --- a/test.ts +++ b/test.ts @@ -5,48 +5,30 @@ import mem, {memDecorator, memClear} from './index.js'; test('memoize', t => { let i = 0; - const fixture = () => i++; + const fixture = (a?: unknown, b?: unknown) => i++; const memoized = mem(fixture); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized(), 0); - // @ts-expect-error t.is(memoized(undefined), 0); - // @ts-expect-error t.is(memoized(undefined), 0); - // @ts-expect-error t.is(memoized('foo'), 1); - // @ts-expect-error t.is(memoized('foo'), 1); - // @ts-expect-error t.is(memoized('foo'), 1); - // @ts-expect-error t.is(memoized('foo', 'bar'), 1); - // @ts-expect-error t.is(memoized('foo', 'bar'), 1); - // @ts-expect-error t.is(memoized('foo', 'bar'), 1); - // @ts-expect-error t.is(memoized(1), 2); - // @ts-expect-error t.is(memoized(1), 2); - // @ts-expect-error t.is(memoized(null), 3); - // @ts-expect-error t.is(memoized(null), 3); - // @ts-expect-error t.is(memoized(fixture), 4); - // @ts-expect-error t.is(memoized(fixture), 4); - // @ts-expect-error t.is(memoized(true), 5); - // @ts-expect-error t.is(memoized(true), 5); // Ensure that functions are stored by reference and not by "value" (e.g. their `.toString()` representation) - // @ts-expect-error t.is(memoized(() => i++), 6); - // @ts-expect-error t.is(memoized(() => i++), 7); }); @@ -63,31 +45,23 @@ test('cacheKey option', t => { test('memoize with multiple non-primitive arguments', t => { let i = 0; - const memoized = mem(() => i++, {cacheKey: JSON.stringify}); + const memoized = mem((a?: unknown, b?: unknown, c?: unknown) => i++, {cacheKey: JSON.stringify}); t.is(memoized(), 0); t.is(memoized(), 0); - // @ts-expect-error t.is(memoized({foo: true}, {bar: false}), 1); - // @ts-expect-error t.is(memoized({foo: true}, {bar: false}), 1); - // @ts-expect-error t.is(memoized({foo: true}, {bar: false}, {baz: true}), 2); - // @ts-expect-error t.is(memoized({foo: true}, {bar: false}, {baz: true}), 2); }); test('memoize with regexp arguments', t => { let i = 0; - const memoized = mem(() => i++, {cacheKey: serializeJavascript}); + const memoized = mem((a?: unknown) => i++, {cacheKey: serializeJavascript}); t.is(memoized(), 0); t.is(memoized(), 0); - // @ts-expect-error t.is(memoized(/Sindre Sorhus/), 1); - // @ts-expect-error t.is(memoized(/Sindre Sorhus/), 1); - // @ts-expect-error t.is(memoized(/Elvin Peng/), 2); - // @ts-expect-error t.is(memoized(/Elvin Peng/), 2); }); @@ -95,38 +69,30 @@ test('memoize with Symbol arguments', t => { let i = 0; const argument1 = Symbol('fixture1'); const argument2 = Symbol('fixture2'); - const memoized = mem(() => i++); + const memoized = mem((a?: unknown) => i++); t.is(memoized(), 0); t.is(memoized(), 0); - // @ts-expect-error t.is(memoized(argument1), 1); - // @ts-expect-error t.is(memoized(argument1), 1); - // @ts-expect-error t.is(memoized(argument2), 2); - // @ts-expect-error t.is(memoized(argument2), 2); }); test('maxAge option', async t => { let i = 0; - const fixture = () => i++; + const fixture = (a?: unknown) => i++; const memoized = mem(fixture, {maxAge: 100}); - // @ts-expect-error t.is(memoized(1), 0); - // @ts-expect-error t.is(memoized(1), 0); await delay(50); - // @ts-expect-error t.is(memoized(1), 0); await delay(200); - // @ts-expect-error t.is(memoized(1), 1); }); test('maxAge option deletes old items', async t => { let i = 0; - const fixture = () => i++; + const fixture = (a?: unknown) => i++; const cache = new Map(); const deleted: number[] = []; const _delete = cache.delete.bind(cache); @@ -135,19 +101,14 @@ test('maxAge option deletes old items', async t => { return _delete(item); }; - // @ts-expect-error const memoized = mem(fixture, {maxAge: 100, cache}); - // @ts-expect-error t.is(memoized(1), 0); - // @ts-expect-error t.is(memoized(1), 0); t.is(cache.has(1), true); await delay(50); - // @ts-expect-error t.is(memoized(1), 0); t.is(deleted.length, 0); await delay(200); - // @ts-expect-error t.is(memoized(1), 1); t.is(deleted.length, 1); t.is(deleted[0], 1); @@ -155,7 +116,7 @@ test('maxAge option deletes old items', async t => { test('maxAge items are deleted even if function throws', async t => { let i = 0; - const fixture = () => { + const fixture = (a?: unknown) => { if (i === 1) { throw new Error('failure'); } @@ -165,17 +126,13 @@ test('maxAge items are deleted even if function throws', async t => { const cache = new Map(); const memoized = mem(fixture, {maxAge: 100, cache}); - // @ts-expect-error t.is(memoized(1), 0); - // @ts-expect-error t.is(memoized(1), 0); t.is(cache.size, 1); await delay(50); - // @ts-expect-error t.is(memoized(1), 0); await delay(200); t.throws(() => { - // @ts-expect-error memoized(1); }, {message: 'failure'}); t.is(cache.size, 0); @@ -198,10 +155,9 @@ test('cache option', t => { test('promise support', async t => { let i = 0; - const memoized = mem(async () => i++); + const memoized = mem(async (a?: unknown) => i++); t.is(await memoized(), 0); t.is(await memoized(), 0); - // @ts-expect-error t.is(await memoized(10), 1); }); diff --git a/tsconfig.json b/tsconfig.json index b6ac98e..b8dfe5b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,8 @@ }, "files": [ "index.ts" - ] + ], + "ts-node": { + "transpileOnly": true + } } From 3afdfaf4f7862f835d01f250779091fe66e00f32 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 14 Nov 2023 14:29:36 +0700 Subject: [PATCH 2/5] Require Node.js 18 --- .github/funding.yml | 1 - .github/workflows/main.yml | 5 ++--- index.ts | 12 ++++++------ package.json | 32 ++++++++++++++++---------------- readme.md | 18 +++--------------- test.ts | 4 ++-- 6 files changed, 29 insertions(+), 43 deletions(-) diff --git a/.github/funding.yml b/.github/funding.yml index 5919969..b5f4cea 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1,2 +1 @@ github: [sindresorhus, fregante] -tidelift: npm/mem diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed4040b..d9ff47b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,10 +12,9 @@ jobs: node-version: #- 20 - 18 - - 16 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/index.ts b/index.ts index 6062813..b945855 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ -import mimicFn from 'mimic-fn'; +import mimicFunction from 'mimic-function'; import mapAgeCleaner from 'map-age-cleaner'; -type AnyFunction = (...arguments_: any) => any; +type AnyFunction = (...arguments_: readonly any[]) => any; const cacheStore = new WeakMap>(); @@ -68,7 +68,7 @@ export type Options< /** [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. -@param fn - Function to be memoized. +@param fn - The function to be memoized. @example ``` @@ -113,7 +113,7 @@ export default function mem< const cacheItem = cache.get(key); if (cacheItem) { - return cacheItem.data; // eslint-disable-line @typescript-eslint/no-unsafe-return + return cacheItem.data; } const result = fn.apply(this, arguments_) as ReturnType; @@ -123,10 +123,10 @@ export default function mem< maxAge: maxAge ? Date.now() + maxAge : Number.POSITIVE_INFINITY, }); - return result; // eslint-disable-line @typescript-eslint/no-unsafe-return + return result; } as FunctionToMemoize; - mimicFn(memoized, fn, { + mimicFunction(memoized, fn, { ignoreNonConfigurable: true, }); diff --git a/package.json b/package.json index 5b70288..5c694f7 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,19 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": "./dist/index.js", + "exports": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "sideEffects": false, "engines": { - "node": ">=16" + "node": ">=18" }, "scripts": { - "test": "xo && ava && npm run build && tsd", + "test": "xo && ava && npm run build && tsd --typings dist/index.d.ts", "build": "del-cli dist && tsc", "prepack": "npm run build" }, - "types": "dist/index.d.ts", "files": [ "dist" ], @@ -39,19 +42,18 @@ ], "dependencies": { "map-age-cleaner": "^0.2.0", - "mimic-fn": "^4.0.0" + "mimic-function": "^5.0.0" }, "devDependencies": { - "@sindresorhus/tsconfig": "^3.0.1", - "@types/serialize-javascript": "^5.0.2", - "ava": "^5.2.0", - "del-cli": "^5.0.0", - "delay": "^5.0.0", + "@sindresorhus/tsconfig": "^5.0.0", + "@types/serialize-javascript": "^5.0.4", + "ava": "^5.3.1", + "del-cli": "^5.1.0", + "delay": "^6.0.0", "serialize-javascript": "^6.0.1", "ts-node": "^10.9.1", - "tsd": "^0.28.1", - "typescript": "^5.0.4", - "xo": "^0.54.2" + "tsd": "^0.29.0", + "xo": "^0.56.0" }, "ava": { "timeout": "1m", @@ -64,9 +66,7 @@ }, "xo": { "rules": { - "@typescript-eslint/member-ordering": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-empty-function": "off" + "@typescript-eslint/no-unsafe-return": "off" } } } diff --git a/readme.md b/readme.md index ca0d771..b4ffe68 100644 --- a/readme.md +++ b/readme.md @@ -12,8 +12,8 @@ If you want to memoize Promise-returning functions (like `async` functions), you ## Install -``` -$ npm install mem +```sh +npm install mem ``` ## Usage @@ -164,7 +164,7 @@ Better yet, if your function’s arguments are compatible with `WeakMap`, you sh Type: `Function` -Function to be memoized. +The function to be memoized. #### options @@ -273,15 +273,3 @@ console.log(cache.stats); ## Related - [p-memoize](https://github.com/sindresorhus/p-memoize) - Memoize promise-returning & async functions - ---- - -
- - Get professional support for this package with a Tidelift subscription - -
- - Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. -
-
diff --git a/test.ts b/test.ts index 41bf19a..41aa6c0 100644 --- a/test.ts +++ b/test.ts @@ -162,7 +162,7 @@ test('promise support', async t => { }); test('preserves the original function name', t => { - t.is(mem(function foo() {}).name, 'foo'); // eslint-disable-line func-names + t.is(mem(function foo() {}).name, 'foo'); // eslint-disable-line func-names, @typescript-eslint/no-empty-function }); test('.clear()', t => { @@ -220,7 +220,7 @@ test('.decorator()', t => { test('memClear() throws when called with a plain function', t => { t.throws(() => { - memClear(() => {}); + memClear(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function }, { message: 'Can\'t clear a function that was not memoized!', instanceOf: TypeError, From 8f207cfa0b241edb670901148de9abb5feed8487 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 14 Nov 2023 22:30:17 +0700 Subject: [PATCH 3/5] Use custom cache expiration logic (#94) --- index.ts | 37 +++++++++++-- package.json | 1 - readme.md | 2 +- test.ts | 149 ++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 157 insertions(+), 32 deletions(-) diff --git a/index.ts b/index.ts index b945855..35bd595 100644 --- a/index.ts +++ b/index.ts @@ -1,9 +1,9 @@ import mimicFunction from 'mimic-function'; -import mapAgeCleaner from 'map-age-cleaner'; -type AnyFunction = (...arguments_: readonly any[]) => any; +type AnyFunction = (...arguments_: readonly any[]) => unknown; const cacheStore = new WeakMap>(); +const cacheTimerStore = new WeakMap>(); type CacheStorageContent = { data: ValueType; @@ -104,8 +104,19 @@ export default function mem< maxAge, }: Options = {}, ): FunctionToMemoize { + if (maxAge === 0) { + return fn; + } + if (typeof maxAge === 'number') { - mapAgeCleaner(cache as unknown as Map>); + const maxSetIntervalValue = 2_147_483_647; + if (maxAge > maxSetIntervalValue) { + throw new TypeError(`The \`maxAge\` option cannot exceed ${maxSetIntervalValue}.`); + } + + if (maxAge < 0) { + throw new TypeError('The `maxAge` option should not be a negative number.'); + } } const memoized = function (this: any, ...arguments_: Parameters): ReturnType { @@ -123,6 +134,18 @@ export default function mem< maxAge: maxAge ? Date.now() + maxAge : Number.POSITIVE_INFINITY, }); + if (typeof maxAge === 'number' && maxAge !== Number.POSITIVE_INFINITY) { + const timer = setTimeout(() => { + cache.delete(key); + }, maxAge); + + timer.unref?.(); + + const timers = cacheTimerStore.get(fn) ?? new Set(); + timers.add(timer as unknown as number); + cacheTimerStore.set(fn, timers); + } + return result; } as FunctionToMemoize; @@ -198,7 +221,7 @@ export function memDecorator< /** Clear all cached data of a memoized function. -@param fn - Memoized function. +@param fn - The memoized function. */ export function memClear(fn: AnyFunction): void { const cache = cacheStore.get(fn); @@ -210,5 +233,9 @@ 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); + } } diff --git a/package.json b/package.json index 5c694f7..096ed43 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "promise" ], "dependencies": { - "map-age-cleaner": "^0.2.0", "mimic-function": "^5.0.0" }, "devDependencies": { diff --git a/readme.md b/readme.md index b4ffe68..3f7bf5a 100644 --- a/readme.md +++ b/readme.md @@ -244,7 +244,7 @@ Clear all cached data of a memoized function. Type: `Function` -Memoized function. +The memoized function. ## Tips diff --git a/test.ts b/test.ts index 41aa6c0..082107e 100644 --- a/test.ts +++ b/test.ts @@ -4,8 +4,8 @@ import serializeJavascript from 'serialize-javascript'; import mem, {memDecorator, memClear} from './index.js'; test('memoize', t => { - let i = 0; - const fixture = (a?: unknown, b?: unknown) => i++; + let index = 0; + const fixture = (a?: unknown, b?: unknown) => index++; const memoized = mem(fixture); t.is(memoized(), 0); t.is(memoized(), 0); @@ -28,13 +28,13 @@ test('memoize', t => { t.is(memoized(true), 5); // Ensure that functions are stored by reference and not by "value" (e.g. their `.toString()` representation) - t.is(memoized(() => i++), 6); - t.is(memoized(() => i++), 7); + t.is(memoized(() => index++), 6); + t.is(memoized(() => index++), 7); }); test('cacheKey option', t => { - let i = 0; - const fixture = (..._arguments: any) => i++; + let index = 0; + const fixture = (..._arguments: any) => index++; const memoized = mem(fixture, {cacheKey: ([firstArgument]) => String(firstArgument)}); t.is(memoized(1), 0); t.is(memoized(1), 0); @@ -44,8 +44,8 @@ test('cacheKey option', t => { }); test('memoize with multiple non-primitive arguments', t => { - let i = 0; - const memoized = mem((a?: unknown, b?: unknown, c?: unknown) => i++, {cacheKey: JSON.stringify}); + let index = 0; + const memoized = mem((a?: unknown, b?: unknown, c?: unknown) => index++, {cacheKey: JSON.stringify}); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized({foo: true}, {bar: false}), 1); @@ -55,8 +55,8 @@ test('memoize with multiple non-primitive arguments', t => { }); test('memoize with regexp arguments', t => { - let i = 0; - const memoized = mem((a?: unknown) => i++, {cacheKey: serializeJavascript}); + let index = 0; + const memoized = mem((a?: unknown) => index++, {cacheKey: serializeJavascript}); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized(/Sindre Sorhus/), 1); @@ -66,10 +66,10 @@ test('memoize with regexp arguments', t => { }); test('memoize with Symbol arguments', t => { - let i = 0; + let index = 0; const argument1 = Symbol('fixture1'); const argument2 = Symbol('fixture2'); - const memoized = mem((a?: unknown) => i++); + const memoized = mem((a?: unknown) => index++); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized(argument1), 1); @@ -79,8 +79,8 @@ test('memoize with Symbol arguments', t => { }); test('maxAge option', async t => { - let i = 0; - const fixture = (a?: unknown) => i++; + let index = 0; + const fixture = (a?: unknown) => index++; const memoized = mem(fixture, {maxAge: 100}); t.is(memoized(1), 0); t.is(memoized(1), 0); @@ -91,8 +91,8 @@ test('maxAge option', async t => { }); test('maxAge option deletes old items', async t => { - let i = 0; - const fixture = (a?: unknown) => i++; + let index = 0; + const fixture = (a?: unknown) => index++; const cache = new Map(); const deleted: number[] = []; const _delete = cache.delete.bind(cache); @@ -115,13 +115,13 @@ test('maxAge option deletes old items', async t => { }); test('maxAge items are deleted even if function throws', async t => { - let i = 0; + let index = 0; const fixture = (a?: unknown) => { - if (i === 1) { + if (index === 1) { throw new Error('failure'); } - return i++; + return index++; }; const cache = new Map(); @@ -139,8 +139,8 @@ test('maxAge items are deleted even if function throws', async t => { }); test('cache option', t => { - let i = 0; - const fixture = (..._arguments: any) => i++; + let index = 0; + const fixture = (..._arguments: any) => index++; const memoized = mem(fixture, { cache: new WeakMap(), cacheKey: ([firstArgument]: [ReturnValue]): ReturnValue => firstArgument, @@ -154,8 +154,8 @@ test('cache option', t => { }); test('promise support', async t => { - let i = 0; - const memoized = mem(async (a?: unknown) => i++); + let index = 0; + const memoized = mem(async (a?: unknown) => index++); t.is(await memoized(), 0); t.is(await memoized(), 0); t.is(await memoized(10), 1); @@ -166,8 +166,8 @@ test('preserves the original function name', t => { }); test('.clear()', t => { - let i = 0; - const fixture = () => i++; + let index = 0; + const fixture = () => index++; const memoized = mem(fixture); t.is(memoized(), 0); t.is(memoized(), 0); @@ -240,3 +240,102 @@ test('memClear() throws when called on an unclearable cache', t => { instanceOf: TypeError, }); }); + +test('maxAge - cache item expires after specified duration', async t => { + let index = 0; + const fixture = () => index++; + const memoized = mem(fixture, {maxAge: 100}); + + t.is(memoized(), 0); // Initial call, cached + t.is(memoized(), 0); // Subsequent call, still cached + await delay(150); // Wait for longer than maxAge + t.is(memoized(), 1); // Cache expired, should compute again +}); + +test('maxAge - cache expiration timing is accurate', async t => { + let index = 0; + const fixture = () => index++; + const memoized = mem(fixture, {maxAge: 100}); + + t.is(memoized(), 0); + await delay(90); // Wait for slightly less than maxAge + t.is(memoized(), 0); // Should still be cached + await delay(20); // Total delay now exceeds maxAge + t.is(memoized(), 1); // Should recompute as cache has expired +}); + +test('maxAge - expired items are not present in cache', async t => { + let index = 0; + const fixture = () => index++; + const cache = new Map(); + const memoized = mem(fixture, {maxAge: 100, cache}); + + memoized(); // Call to cache the result + await delay(150); // Wait for cache to expire + memoized(); // Recompute and recache + t.is(cache.size, 1); // Only one item should be in the cache +}); + +test('maxAge - complex arguments and cache expiration', async t => { + let index = 0; + const fixture = object => index++; + const memoized = mem(fixture, {maxAge: 100, cacheKey: JSON.stringify}); + + const arg = {key: 'value'}; + t.is(memoized(arg), 0); + await delay(150); + t.is(memoized(arg), 1); // Argument is the same, but should recompute due to expiration +}); + +test('maxAge - concurrent calls return cached value', async t => { + let index = 0; + const fixture = () => index++; + const memoized = mem(fixture, {maxAge: 100}); + + t.is(memoized(), 0); + await delay(50); // Delay less than maxAge + t.is(memoized(), 0); // Should return cached value +}); + +test('maxAge - different arguments have separate expirations', async t => { + let index = 0; + const fixture = x => index++; + const memoized = mem(fixture, {maxAge: 100}); + + t.is(memoized('a'), 0); + await delay(150); // Expire the cache for 'a' + t.is(memoized('b'), 1); // 'b' should be a separate cache entry + t.is(memoized('a'), 2); // 'a' should be recomputed +}); + +test('maxAge - zero maxAge means no caching', t => { + let index = 0; + const fixture = () => index++; + const memoized = mem(fixture, {maxAge: 0}); + + t.is(memoized(), 0); + t.is(memoized(), 1); // No caching, should increment +}); + +test('maxAge - immediate expiration', async t => { + let index = 0; + const fixture = () => index++; + const memoized = mem(fixture, {maxAge: 1}); + t.is(memoized(), 0); + await delay(10); + t.is(memoized(), 1); // Cache should expire immediately +}); + +test('maxAge - high concurrency', async t => { + let index = 0; + const fixture = () => index++; + const memoized = mem(fixture, {maxAge: 50}); + + // Simulate concurrent calls + for (let job = 0; job < 10_000; job++) { + memoized(); + } + + await delay(100); + t.is(memoized(), 1); +}); From 475689280bb4c9047ae1a13a799b0cae375b1294 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 14 Nov 2023 22:43:18 +0700 Subject: [PATCH 4/5] Rename package from `mem` to `memoize` --- index.ts | 28 +++++++++--------- package.json | 6 ++-- readme.md | 67 ++++++++++++++++++++++++------------------ test-d/index.test-d.ts | 26 ++++++++-------- test.ts | 58 ++++++++++++++++++------------------ 5 files changed, 98 insertions(+), 87 deletions(-) diff --git a/index.ts b/index.ts index 35bd595..2826a2d 100644 --- a/index.ts +++ b/index.ts @@ -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] @@ -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 @@ -93,7 +93,7 @@ memoized('bar'); //=> 2 ``` */ -export default function mem< +export default function memoize< FunctionToMemoize extends AnyFunction, CacheKeyType, >( @@ -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; } @@ -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, >( @@ -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; } @@ -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!'); @@ -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); diff --git a/package.json b/package.json index 096ed43..f8dd802 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/readme.md b/readme.md index 3f7bf5a..cb98811 100644 --- a/readme.md +++ b/readme.md @@ -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 @@ -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 @@ -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 @@ -64,21 +64,21 @@ 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 @@ -86,7 +86,9 @@ await memGot('https://sindresorhus.com'); 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 @@ -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(',') }); @@ -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 @@ -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 @@ -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 @@ -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 }); @@ -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 @@ -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; } @@ -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. @@ -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} diff --git a/test-d/index.test-d.ts b/test-d/index.test-d.ts index c698ce5..d043730 100644 --- a/test-d/index.test-d.ts +++ b/test-d/index.test-d.ts @@ -1,14 +1,14 @@ 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(mem(fn)); -expectType(mem(fn, {maxAge: 1})); -expectType(mem(fn, {cacheKey: ([firstArgument]: [string]) => firstArgument})); +expectType(memoize(fn)); +expectType(memoize(fn, {maxAge: 1})); +expectType(memoize(fn, {cacheKey: ([firstArgument]: [string]) => firstArgument})); expectType( - 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}>(), @@ -16,7 +16,7 @@ expectType( ); expectType( // The `firstArgument` of `fn` is of type `string`, so it's used - mem(fn, {cache: new Map()}), + memoize(fn, {cache: new Map()}), ); /* Overloaded function tests */ @@ -26,29 +26,29 @@ function overloadedFn(parameter: boolean): boolean { return parameter; } -expectType(mem(overloadedFn)); -expectType(mem(overloadedFn)(true)); -expectType(mem(overloadedFn)(false)); +expectType(memoize(overloadedFn)); +expectType(memoize(overloadedFn)(true)); +expectType(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(); diff --git a/test.ts b/test.ts index 082107e..e2e086c 100644 --- a/test.ts +++ b/test.ts @@ -1,12 +1,12 @@ import test from 'ava'; import delay from 'delay'; import serializeJavascript from 'serialize-javascript'; -import mem, {memDecorator, memClear} from './index.js'; +import memoize, {memoizeDecorator, memoizeClear} from './index.js'; test('memoize', t => { let index = 0; const fixture = (a?: unknown, b?: unknown) => index++; - const memoized = mem(fixture); + const memoized = memoize(fixture); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized(), 0); @@ -35,7 +35,7 @@ test('memoize', t => { test('cacheKey option', t => { let index = 0; const fixture = (..._arguments: any) => index++; - const memoized = mem(fixture, {cacheKey: ([firstArgument]) => String(firstArgument)}); + const memoized = memoize(fixture, {cacheKey: ([firstArgument]) => String(firstArgument)}); t.is(memoized(1), 0); t.is(memoized(1), 0); t.is(memoized('1'), 0); @@ -45,7 +45,7 @@ test('cacheKey option', t => { test('memoize with multiple non-primitive arguments', t => { let index = 0; - const memoized = mem((a?: unknown, b?: unknown, c?: unknown) => index++, {cacheKey: JSON.stringify}); + const memoized = memoize((a?: unknown, b?: unknown, c?: unknown) => index++, {cacheKey: JSON.stringify}); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized({foo: true}, {bar: false}), 1); @@ -56,7 +56,7 @@ test('memoize with multiple non-primitive arguments', t => { test('memoize with regexp arguments', t => { let index = 0; - const memoized = mem((a?: unknown) => index++, {cacheKey: serializeJavascript}); + const memoized = memoize((a?: unknown) => index++, {cacheKey: serializeJavascript}); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized(/Sindre Sorhus/), 1); @@ -69,7 +69,7 @@ test('memoize with Symbol arguments', t => { let index = 0; const argument1 = Symbol('fixture1'); const argument2 = Symbol('fixture2'); - const memoized = mem((a?: unknown) => index++); + const memoized = memoize((a?: unknown) => index++); t.is(memoized(), 0); t.is(memoized(), 0); t.is(memoized(argument1), 1); @@ -81,7 +81,7 @@ test('memoize with Symbol arguments', t => { test('maxAge option', async t => { let index = 0; const fixture = (a?: unknown) => index++; - const memoized = mem(fixture, {maxAge: 100}); + const memoized = memoize(fixture, {maxAge: 100}); t.is(memoized(1), 0); t.is(memoized(1), 0); await delay(50); @@ -101,7 +101,7 @@ test('maxAge option deletes old items', async t => { return _delete(item); }; - const memoized = mem(fixture, {maxAge: 100, cache}); + const memoized = memoize(fixture, {maxAge: 100, cache}); t.is(memoized(1), 0); t.is(memoized(1), 0); t.is(cache.has(1), true); @@ -125,7 +125,7 @@ test('maxAge items are deleted even if function throws', async t => { }; const cache = new Map(); - const memoized = mem(fixture, {maxAge: 100, cache}); + const memoized = memoize(fixture, {maxAge: 100, cache}); t.is(memoized(1), 0); t.is(memoized(1), 0); t.is(cache.size, 1); @@ -141,7 +141,7 @@ test('maxAge items are deleted even if function throws', async t => { test('cache option', t => { let index = 0; const fixture = (..._arguments: any) => index++; - const memoized = mem(fixture, { + const memoized = memoize(fixture, { cache: new WeakMap(), cacheKey: ([firstArgument]: [ReturnValue]): ReturnValue => firstArgument, }); @@ -155,23 +155,23 @@ test('cache option', t => { test('promise support', async t => { let index = 0; - const memoized = mem(async (a?: unknown) => index++); + const memoized = memoize(async (a?: unknown) => index++); t.is(await memoized(), 0); t.is(await memoized(), 0); t.is(await memoized(10), 1); }); test('preserves the original function name', t => { - t.is(mem(function foo() {}).name, 'foo'); // eslint-disable-line func-names, @typescript-eslint/no-empty-function + t.is(memoize(function foo() {}).name, 'foo'); // eslint-disable-line func-names, @typescript-eslint/no-empty-function }); test('.clear()', t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture); + const memoized = memoize(fixture); t.is(memoized(), 0); t.is(memoized(), 0); - memClear(memoized); + memoizeClear(memoized); t.is(memoized(), 1); t.is(memoized(), 1); }); @@ -184,7 +184,7 @@ test('prototype support', t => { } } - Unicorn.prototype.foo = mem(Unicorn.prototype.foo); + Unicorn.prototype.foo = memoize(Unicorn.prototype.foo); const unicorn = new Unicorn(); @@ -198,12 +198,12 @@ test('.decorator()', t => { const returnValue2 = 101; class TestClass { - @memDecorator() + @memoizeDecorator() counter() { return returnValue++; } - @memDecorator() + @memoizeDecorator() counter2() { return returnValue2; } @@ -220,7 +220,7 @@ test('.decorator()', t => { test('memClear() throws when called with a plain function', t => { t.throws(() => { - memClear(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function + memoizeClear(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function }, { message: 'Can\'t clear a function that was not memoized!', instanceOf: TypeError, @@ -229,12 +229,12 @@ test('memClear() throws when called with a plain function', t => { test('memClear() throws when called on an unclearable cache', t => { const fixture = () => 1; - const memoized = mem(fixture, { + const memoized = memoize(fixture, { cache: new WeakMap(), }); t.throws(() => { - memClear(memoized); + memoizeClear(memoized); }, { message: 'The cache Map can\'t be cleared!', instanceOf: TypeError, @@ -244,7 +244,7 @@ test('memClear() throws when called on an unclearable cache', t => { test('maxAge - cache item expires after specified duration', async t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture, {maxAge: 100}); + const memoized = memoize(fixture, {maxAge: 100}); t.is(memoized(), 0); // Initial call, cached t.is(memoized(), 0); // Subsequent call, still cached @@ -255,7 +255,7 @@ test('maxAge - cache item expires after specified duration', async t => { test('maxAge - cache expiration timing is accurate', async t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture, {maxAge: 100}); + const memoized = memoize(fixture, {maxAge: 100}); t.is(memoized(), 0); await delay(90); // Wait for slightly less than maxAge @@ -268,7 +268,7 @@ test('maxAge - expired items are not present in cache', async t => { let index = 0; const fixture = () => index++; const cache = new Map(); - const memoized = mem(fixture, {maxAge: 100, cache}); + const memoized = memoize(fixture, {maxAge: 100, cache}); memoized(); // Call to cache the result await delay(150); // Wait for cache to expire @@ -279,7 +279,7 @@ test('maxAge - expired items are not present in cache', async t => { test('maxAge - complex arguments and cache expiration', async t => { let index = 0; const fixture = object => index++; - const memoized = mem(fixture, {maxAge: 100, cacheKey: JSON.stringify}); + const memoized = memoize(fixture, {maxAge: 100, cacheKey: JSON.stringify}); const arg = {key: 'value'}; t.is(memoized(arg), 0); @@ -290,7 +290,7 @@ test('maxAge - complex arguments and cache expiration', async t => { test('maxAge - concurrent calls return cached value', async t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture, {maxAge: 100}); + const memoized = memoize(fixture, {maxAge: 100}); t.is(memoized(), 0); await delay(50); // Delay less than maxAge @@ -300,7 +300,7 @@ test('maxAge - concurrent calls return cached value', async t => { test('maxAge - different arguments have separate expirations', async t => { let index = 0; const fixture = x => index++; - const memoized = mem(fixture, {maxAge: 100}); + const memoized = memoize(fixture, {maxAge: 100}); t.is(memoized('a'), 0); await delay(150); // Expire the cache for 'a' @@ -311,7 +311,7 @@ test('maxAge - different arguments have separate expirations', async t => { test('maxAge - zero maxAge means no caching', t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture, {maxAge: 0}); + const memoized = memoize(fixture, {maxAge: 0}); t.is(memoized(), 0); t.is(memoized(), 1); // No caching, should increment @@ -320,7 +320,7 @@ test('maxAge - zero maxAge means no caching', t => { test('maxAge - immediate expiration', async t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture, {maxAge: 1}); + const memoized = memoize(fixture, {maxAge: 1}); t.is(memoized(), 0); await delay(10); t.is(memoized(), 1); // Cache should expire immediately @@ -329,7 +329,7 @@ test('maxAge - immediate expiration', async t => { test('maxAge - high concurrency', async t => { let index = 0; const fixture = () => index++; - const memoized = mem(fixture, {maxAge: 50}); + const memoized = memoize(fixture, {maxAge: 50}); // Simulate concurrent calls for (let job = 0; job < 10_000; job++) { From 2a8997ededd818a07edab1a750cf969ba473de3c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 14 Nov 2023 22:46:26 +0700 Subject: [PATCH 5/5] 10.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8dd802..768d0c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "memoize", - "version": "9.0.2", + "version": "10.0.0", "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/memoize",