limit-once
Gives you the ability to ensure a function
is only called "once"
, and that that the result of that single function
call is returned every time.
[!NOTE]
This package is still under construction
Features:
Installation
yarn add limit-once
npm install limit-once
bun add limit-once
Synchronous variant
Create a new function
that wraps an existing function, where the wrapped function is only called once.
import { once } from 'limit-once';
function getGreeting(name: string): string {
return `Hello ${name}`;
}
const getGreetingOnce = once(getGreeting);
getGreetingOnce('Alex');
getGreetingOnce('Sam');
getGreetingOnce('Greg');
If the function being wrapped throw
s an error, then that throw
is not cached, and the wrapped function is allowed to be called again
import { once } from 'limit-once';
let callCount = 0;
function maybeThrow({ shouldThrow }: { shouldThrow: boolean }): string {
callCount++;
if (shouldThrow) {
throw new Error(`Call count: ${callCount}`);
}
return `Call count: ${callCount}`;
}
const maybeThrowOnce = once(maybeThrow);
expect(() => maybeThrowOnce({ shouldThrow: true })).toThrowError('Call count: 1');
expect(() => maybeThrowOnce({ shouldThrow: true })).toThrowError('Call count: 2');
expect(maybeThrowOnce({ shouldThrow: false })).toBe('Call count: 3');
expect(maybeThrowOnce({ shouldThrow: false })).toBe('Call count: 3');
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 function getLoggedInUser() {
await fetch('/user').json();
}
const getLoggedInUserOnce = onceAsync(getLoggedInUser);
const user1 = await getLoggedInUserOnce();
const user2 = await getLoggedInUserOnce();
If the wrapped function that returns a promise has it's promise "rejected"
, then the call will not be cached, and the underlying function can be called again.
import { onceAsync } from 'limit-once';
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 = onceAsync(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 function getLoggedInUser() {
await fetch('/user').json();
}
export const getLoggedInUserOnce = onceAsync(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';
let callCount = 0;
async function getCallCount(): Promise<string> {
return `Call count: ${callCount}`;
}
const onced = onceAsync(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(s) that the onced function has returned will be rejected.
import { onceAsync } from 'limit-once';
async function getName(): Promise<string> {
return 'Alex';
}
const getNameOnce = onceAsync(getName);
const promise1 = getNameOnce().catch(() => {
console.log('rejected promise 1');
});
const promise2 = getNameOnce().catch(() => {
console.log('rejected promise 2');
});
getNameOnce.clear();