Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ export type Options<
readonly cacheKey?: (arguments_: Parameters<FunctionToMemoize>) => CacheKeyType;

/**
Use 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. To disable caching so that only concurrent executions resolve with the same value, pass `false`.
Use 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.

To disable caching so that only concurrent executions resolve with the same value, pass `false` or `'while-pending'`.

@default new Map()
@example new WeakMap()
*/
readonly cache?: CacheStorage<CacheKeyType, AsyncReturnType<FunctionToMemoize>> | false;
readonly cache?: CacheStorage<CacheKeyType, AsyncReturnType<FunctionToMemoize>> | false | 'while-pending';

/**
Controls whether a fulfilled value should be written to the cache.
Expand Down Expand Up @@ -137,6 +139,9 @@ export default function pMemoize<
cache = new Map<CacheKeyType, AsyncReturnType<FunctionToMemoize>>(),
} = options ?? {};

// Normalize 'while-pending' to false for internal use
const normalizedCache = cache === 'while-pending' ? false : cache;

// Promise objects can't be serialized so we keep track of them internally and only provide their resolved values to `cache`
// `Promise<AsyncReturnType<FunctionToMemoize>>` is used instead of `ReturnType<FunctionToMemoize>` because promise properties are not kept
const promiseCache = new Map<CacheKeyType, Promise<AsyncReturnType<FunctionToMemoize>>>();
Expand All @@ -150,8 +155,8 @@ export default function pMemoize<

const promise = (async () => {
try {
if (cache && await cache.has(key)) {
return (await cache.get(key)) as AsyncReturnType<FunctionToMemoize>;
if (normalizedCache && await normalizedCache.has(key)) {
return (await normalizedCache.get(key)) as AsyncReturnType<FunctionToMemoize>;
}

const promise = fn.apply(this, arguments_) as Promise<AsyncReturnType<FunctionToMemoize>>;
Expand All @@ -161,13 +166,13 @@ export default function pMemoize<
try {
return result;
} finally {
if (cache) {
if (normalizedCache) {
const allow = options?.shouldCache
? await options.shouldCache(result, {key, argumentsList: arguments_})
: true;

if (allow) {
await cache.set(key, result);
await normalizedCache.set(key, result);
}
}
}
Expand All @@ -185,7 +190,7 @@ export default function pMemoize<
ignoreNonConfigurable: true,
});

cacheStore.set(memoized, cache);
cacheStore.set(memoized, normalizedCache);

return memoized;
}
Expand Down
6 changes: 4 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ See the [caching strategy](#caching-strategy) section for more information.

##### cache

Type: `object | false`\
Type: `object | false | 'while-pending'`\
Default: `new Map()`

Use 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. To disable caching so that only concurrent executions resolve with the same value, pass `false`.
Use 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.

To disable caching so that only concurrent executions resolve with the same value, pass `false` or `'while-pending'`.

See the [caching strategy](https://github.com/sindresorhus/mem#caching-strategy) section in the `mem` package for more information.

Expand Down
1 change: 1 addition & 0 deletions test-d/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ expectType<typeof fn>(pMemoize(fn, {
}));
expectType<typeof fn>(pMemoize(fn, {cache: new Map<string, boolean>()}));
expectType<typeof fn>(pMemoize(fn, {cache: false}));
expectType<typeof fn>(pMemoize(fn, {cache: 'while-pending'}));

/* Overloaded function tests */
async function overloadedFn(parameter: false): Promise<false>;
Expand Down
25 changes: 25 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ test('disables caching', async t => {
t.deepEqual(await Promise.all([memoized(), memoized()]), [3, 3]);
});

test('cache: "while-pending" disables caching but deduplicates in-flight requests', async t => {
let index = 0;

const memoized = pMemoize(async () => index++, {cache: 'while-pending'});

t.is(await memoized(), 0);
t.is(await memoized(), 1);
t.is(await memoized(), 2);
t.deepEqual(await Promise.all([memoized(), memoized()]), [3, 3]);
});

test('.pMemoizeClear()', async t => {
let index = 0;
const fixture = async () => index++;
Expand Down Expand Up @@ -297,6 +308,20 @@ test('.pMemoizeClear() throws when called on a disabled cache', t => {
});
});

test('.pMemoizeClear() throws when called on a while-pending cache', t => {
const fixture = async () => 1;
const memoized = pMemoize(fixture, {
cache: 'while-pending',
});

t.throws(() => {
pMemoizeClear(memoized);
}, {
message: 'Can\'t clear a function that doesn\'t use a cache!',
instanceOf: TypeError,
});
});

test.serial('shouldCache: skips cache.set when predicate false', async t => {
let calls = 0;
const cache = new Map<unknown, unknown>();
Expand Down