limit-once
Create a once
function that caches the result of the first function call. limit-once
let's you lazily evaluate a value (using a function), and then hold onto the value forever.
[!NOTE]
This package is still under construction
Features:
- synchronous variant (
0.2 Kb
) - asynchronous variant for promises (
1Kb
) - only include the code for the variant(s) you want
- both variants support cache clearing
Installation
yarn add limit-once
npm install limit-once
bun add limit-once
Synchronous variant
import { once } from 'limit-once';
function getGreeting(name: string): string {
return `Hello ${name}`;
}
const getGreetingOnce = once(getGreeting);
getGreetingOnce('Alex');
getGreetingOnce('Sam');
getGreetingOnce('Greg');
Cache clearing (.clear()
)
You can clear the cache of a onced function by using the .clear()
function property.
import { once } from 'limit-once';
function getGreeting(name: string): string {
return `Hello ${name}`;
}
const getGreetingOnce = once(getGreeting);
getGreetingOnce('Alex');
getGreetingOnce('Sam');
getGreetingOnce.clear();
getGreetingOnce('Greg');
Asynchronous variant
Our async variant allows you to have a once
functionality for functions that Promise
.
import { onceAsync } from 'limit-once/async';
async function getLoggedInUser() {
await fetch('/user').json();
}
const getLoggedInUserOnce = asyncOnce(getLoggedInUser);
const user1 = await getLoggedInUserOnce();
const user2 = await getLoggedInUserOnce();
A "rejected" promise call will not be cached and will allow the wrapped function to be called again
import { onceAsync } from 'limit-once/async';
let callCount = 0;
async function maybeThrow({ shouldThrow }: { shouldThrow: boolean }): Promise<string> {
callCount++;
if (shouldThrow) {
throw new Error(`Call count: ${callCount}`);
}
return `Call count: ${callCount}`;
}
const maybeThrowOnce = asyncOnce(maybeThrow);
expect(async () => await maybeThrowOnce({ shouldThrow: true })).toThrowError('Call count: 1');
expect(async () => await maybeThrowOnce({ shouldThrow: true })).toThrowError('Call count: 2');
expect(await maybeThrowOnce({ shouldThrow: false })).toBe('Call count: 3');
expect(await maybeThrowOnce({ shouldThrow: false })).toBe('Call count: 3');
If multiple calls are made to the onced function while the original promise is still "pending"
, then the original promise is re-used. This prevents multiple calls to the underlying function.
import { onceAsync } from 'limit-once/async';
async function getLoggedInUser() {
await fetch('/user').json();
}
export const getLoggedInUserOnce = asyncOnce(getLoggedInUser);
const promise1 = getLoggedInUserOnce();
const promise2 = getLoggedInUserOnce();
console.log(promise1 === promise2);
Cache clearing (.clear()
)
You can clear the cache of a onced async function by using the .clear()
function property.
import { onceAsync } from 'limit-once/async';
let callCount = 0;
async function getCallCount(): Promise<string> {
return `Call count: ${callCount}`;
}
const onced = asyncOnce(getCallCount);
expect(await onced({ shouldThrow: false })).toBe('Call count: 1');
expect(await onced({ shouldThrow: false })).toBe('Call count: 1');
onced.clear();
expect(await onced({ shouldThrow: false })).toBe('Call count: 2');
expect(await onced({ shouldThrow: false })).toBe('Call count: 2');
If onced async function is "pending"
when .clear()
is called, then the promise will be rejected.
import { onceAsync } from 'limit-once/async';
async function getName(): Promise<string> {
return 'Alex';
}
const getNameOnce = asyncOnce(getName);
const promise1 = getNameOnce().catch(() => {
console.log('rejected');
});
getNameOnce.clear();