Skip to content

once

once wraps an async function so that it only executes on the first call. Subsequent calls return the cached result. Concurrent calls during the first execution share the same in-flight promise.

Best for one-time initialization Use once when a resource should be initialized only once, such as database connections, configuration loading, or singleton setup.

Root package:

import { once } from "@vgerbot/async";

Module subpath:

import { once } from "@vgerbot/async/utils";

Leaf subpath:

import { once } from "@vgerbot/async/utils/once";
import { once } from "@vgerbot/async";
let callCount = 0;
const initialize = once(async (token) => {
callCount++;
await token.sleep(100);
return { connection: "established" };
});
const result1 = await initialize().promise; // callCount = 1
const result2 = await initialize().promise; // callCount = 1 (cached)
const result3 = await initialize().promise; // callCount = 1 (cached)
console.log(result1 === result2); // true (same object)
  • Singleton initialization: Ensure a resource is created only once.
  • Lazy loading: Defer initialization until first use, then cache.
  • Configuration loading: Load configuration once and reuse.
  • Connection management: Open a database connection once.
function once<T>(
fn: AsyncTask<T>,
options?: CancellableOptions<T>,
): () => CancellableHandle<T>;
Parameter Type Default Description
fn AsyncTask<T> The async task to execute once. Receives a CancellableToken.
options CancellableOptions<T> undefined Cancellable configuration options.

Returns a function that returns a CancellableHandle<T>. On the first call, the task is executed. On subsequent calls, a CancellableHandle resolving to the cached result is returned.

  1. First call: The task is executed via cancellable. The in-flight CancellableHandle is stored.
  2. Concurrent calls: While the first execution is in progress, concurrent calls return the same in-flight CancellableHandle.
  3. After completion: The result is cached. All subsequent calls return a new CancellableHandle that resolves to the cached value.
  4. Error handling: If the first execution fails, the error is not cached — subsequent calls will retry.
const connect = once(async (token) => {
const conn = await openConnection();
return conn;
});
// Multiple concurrent calls share the same in-flight promise
const [conn1, conn2, conn3] = await Promise.all([
connect().promise,
connect().promise,
connect().promise,
]);
console.log(conn1 === conn2); // true

The first execution’s CancellableHandle can be cancelled. Subsequent calls return a new handle wrapping the cached result (or the in-flight promise if still running).

const init = once(async (token) => {
await token.sleep(5000);
return "initialized";
});
const handle = init();
handle.cancel("User cancelled");
// Retry — new execution since the first was cancelled
const result = await init().promise;

once is generic over T. The returned function takes no arguments (the task receives only a CancellableToken).

const getConfig = once(async (token) => {
const res = await token.wrap(fetch("/api/config"));
return res.json() as Promise<{ apiUrl: string }>;
});
const config = await getConfig().promise; // { apiUrl: string }