limit-once
Cache the first successful result of a function
call.
Features
Installation
yarn add limit-once
npm install limit-once
bun add limit-once
Synchronous variant
Create a new function
that only allows an existing function
to be 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');
[!NOTE]
If you want the wrapped function to cache the result, but recompute the result when the arguments change: use memoize-one
Only successful calls are cached
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 (once(fn).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
Allows you to have a once
functionality for a function
that returns a Promise
.
import { onceAsync } from 'limit-once';
async function getPermissions(): Promise<Record<string, boolean>> {
const response = await fetch('/permissions');
return await response.json();
}
const getPermissionsOnce = onceAsync(getPermissions);
const user1 = await getPermissionsOnce();
const user2 = await getPermissionsOnce();
Only "fulfilled"
Promises
are cached
If the wrapped function
has it's Promise
"rejected"
, then the "rejected"
Promise
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');
Calls while a Promise
is "pending"
If multiple calls are made to a onceAsync(fn)
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 getPermissions(): Promise<Record<string, boolean>> {
const response = await fetch('/permissions');
return await response.json();
}
export const getPermissionsOnce = onceAsync(getPermissions);
const promise1 = getPermissionsOnce();
const promise2 = getPermissionsOnce();
console.log(promise1 === promise2);
Cache clearing (onceAsync(fn).clear()
)
You can clear the cache of a onceAsync
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();
Outputs
limit-once
is a dual package that supports ECMAScript modules (esm) and CommonJS (cjs)- Distributed files are compiled to "es6" which has wide support
- Internet Explorer 11 not supported (it does not support "es6")