Aller au contenu principal

once()

once<Args, Result>(fn): (...args) => Result

Creates a function that can only be called once.

remarque

Subsequent calls return the cached result and ignore new arguments.


Type Parameters

Args: Args extends unknown[]

The argument types of the function.

Result: Result

The return type of the function.


Parameters

fn: (...args) => Result

The function to restrict to one execution.


Returns

The restricted function that caches the first call's result.


Since

2.0.0


Performance

O(1) lookup after first call. Single boolean flag check for optimal performance.


Also known as

once (Lodash, es-toolkit, Remeda, Radashi, Ramda) · ❌ (Effect, Modern Dash, Antfu)


Example

// Basic usage
const initialize = once(() => {
console.log('Initializing...');
return 'initialized';
});

initialize(); // "Initializing..." -> "initialized"
initialize(); // "initialized" (cached)

// With parameters (ignores new args after first call)
const createUser = once((name: string) => ({ id: 1, name }));

createUser('John'); // { id: 1, name: 'John' }
createUser('Jane'); // { id: 1, name: 'John' } (cached)

// Async functions
const fetchData = once(async () => fetch('/api/data'));
await fetchData(); // Fetches
await fetchData(); // Cached promise

How it works?

Creates a function that executes only once — subsequent calls return the cached result.

Simple Flow

Key Behavior

New arguments are ignored after the first call:


Use Cases

Initialize resources only once 📌

Ensure initialization code runs only once to prevent duplicate setup. Essential for resource management and preventing initialization errors.

const initApp = once(() => {
console.log("App initialized");
// ... setup code
});

initApp(); // "App initialized"
initApp(); // (ignored)

Create singletons safely

Ensure singleton instances are created only once. Essential for state management and ensuring single instance patterns.

const getDb = once(() => new DatabaseConnection());
const db1 = getDb();
const db2 = getDb();

console.log(db1 === db2); // true

Lazy-initialize an SDK or API client

Initialize a third-party SDK on first use instead of at app startup. The standard pattern for Stripe, Firebase, Analytics, or any heavy client library.

const getStripe = once(() => {
return loadStripe(process.env.STRIPE_PUBLIC_KEY);
});

const getAnalytics = once(() => {
return initializeAnalytics({ trackingId: "UA-XXXXX" });
});

// Called from multiple components — SDK loads only on first call
async function handleCheckout() {
const stripe = await getStripe();
stripe.redirectToCheckout({ sessionId });
}

Request browser permission only once

Ensure a permission prompt is shown only once per session. Critical for notifications, geolocation, and camera access in PWAs.

const requestNotificationPermission = once(async () => {
const result = await Notification.requestPermission();
return result === "granted";
});

// Multiple components can call this safely
async function enableNotifications() {
const granted = await requestNotificationPermission();
if (granted) {
subscribeToUpdates();
}
}

Initialize focus trap on a dialog element

Set up focus trapping once when a modal opens to keep keyboard navigation inside. Critical for accessible dialogs and overlays in design systems.

// Use once PER dialog instance, not globally
const createFocusTrap = (dialog: HTMLElement) => {
const init = once(() => {
const focusableEls = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstEl = focusableEls[0] as HTMLElement;
const lastEl = focusableEls[focusableEls.length - 1] as HTMLElement;

dialog.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.key !== "Tab") return;
if (e.shiftKey && document.activeElement === firstEl) {
e.preventDefault();
lastEl.focus();
} else if (!e.shiftKey && document.activeElement === lastEl) {
e.preventDefault();
firstEl.focus();
}
});

firstEl.focus();
return { firstEl, lastEl };
});

return init;
};

// Each dialog gets its own once-guarded init
const trap = createFocusTrap(dialogElement);
trap(); // Initializes
trap(); // Ignored (already initialized)

Create a live announcer singleton for screen readers

Initialize an ARIA live region once for dynamic announcements. Essential for SPAs where route changes and async updates need to be announced.

const getLiveAnnouncer = once(() => {
const el = document.createElement("div");
el.setAttribute("aria-live", "polite");
el.setAttribute("aria-atomic", "true");
el.className = "sr-only";
document.body.appendChild(el);
return el;
});

const announce = (message: string) => {
const announcer = getLiveAnnouncer();
announcer.textContent = "";
requestAnimationFrame(() => {
announcer.textContent = message;
});
};

// Route change
announce("Dashboard page loaded");
// Async operation
announce("3 new notifications");

Detect platform capabilities once at startup

Run platform detection once and cache the result for the app lifetime. Essential for cross-browser components that adapt to the environment.

const detectPlatform = once(() => ({
isTouchDevice: "ontouchstart" in window || navigator.maxTouchPoints > 0,
supportsPassive: (() => {
let passive = false;
try {
const opts = Object.defineProperty({}, "passive", { get: () => (passive = true) });
window.addEventListener("test", null as never, opts);
} catch {}
return passive;
})(),
prefersReducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
isHighContrast: window.matchMedia("(forced-colors: active)").matches,
}));

// Used across the entire design system
const platform = detectPlatform();
if (platform.prefersReducedMotion) {
disableAnimations();
}

Initialize clipboard listener once

Set up clipboard event handling once for a rich text editor. Perfect for editors and code blocks with custom copy/paste behavior.

const initClipboard = once((editor: HTMLElement) => {
editor.addEventListener("copy", (e: ClipboardEvent) => {
const selection = window.getSelection()?.toString() ?? "";
e.clipboardData?.setData("text/plain", selection);
e.clipboardData?.setData("text/html", editor.innerHTML);
e.preventDefault();
});

editor.addEventListener("paste", (e: ClipboardEvent) => {
const html = e.clipboardData?.getData("text/html");
const text = e.clipboardData?.getData("text/plain") ?? "";
insertContent(html ?? text);
e.preventDefault();
});
});

// Safe to call on every editor mount
initClipboard(editorElement);

Register a service worker once at startup

Register the service worker only on first call, even if multiple modules trigger it. Essential for PWA initialization without duplicate registrations.

const registerSW = once(async () => {
if ("serviceWorker" in navigator) {
const registration = await navigator.serviceWorker.register("/sw.js");
console.log("SW registered:", registration.scope);
return registration;
}
return null;
});

// Safe to call from multiple entry points
await registerSW();