Skip to main content

Run one-shot and parallel hooks

When you need to ensure a setup task runs exactly once or trigger multiple independent operations simultaneously, hookable provides specialized execution patterns. Using hookOnce prevents redundant logic from firing on subsequent events, while callHookParallel allows you to await multiple asynchronous handlers without blocking them behind each other.

One-Shot Hook Execution

If you register a handler that should only respond to the very first occurrence of an event—such as initializing a shared resource or logging a startup metric—use hookOnce. This method automatically removes the callback after its first execution, ensuring that subsequent calls to callHook do not trigger it again.

import { createHooks } from 'hookable';

interface MyHooks {
'init': () => void;
}

async function runExample() {
const hooks = createHooks<MyHooks>();
let callCount = 0;

// Register a handler that should only run once
hooks.hookOnce('init', () => {
callCount++;
});

// First call triggers the handler
await hooks.callHook('init');

// Second call does nothing as the handler was automatically removed
await hooks.callHook('init');

if (callCount !== 1) {
throw new Error(`Expected 1 call, but got ${callCount}`);
}
}

await runExample();

Parallel Execution and Manual Removal

For performance-critical paths where handlers do not depend on each other's results, callHookParallel dispatches all registered callbacks at once. This is ideal for notification systems or side-effect tracking. If you need to stop a specific handler from receiving future events, you can use removeHook by passing the original named function reference.

import { createHooks } from 'hookable';

type HookResult = void | Promise<void>;

interface MyHooks {
'notify': () => HookResult;
}

async function runParallelExample() {
const hooks = createHooks<MyHooks>();
let active = false;

// Define a named handler for later removal
const onNotify = () => {
active = true;
};

// Register the handler
hooks.hook('notify', onNotify);

// Execute all 'notify' hooks in parallel
await hooks.callHookParallel('notify');

// Remove the specific handler using its reference
hooks.removeHook('notify', onNotify);

// Reset state and call again to verify removal
active = false;
await hooks.callHookParallel('notify');

if (active) {
throw new Error('Handler was not removed');
}
}

await runParallelExample();

When using callHookParallel, the execution returns a promise that resolves once all handlers have finished their work. If any handler returns a promise, the parallel dispatcher waits for all of them to settle. Note that while callHookParallel ensures all handlers start, it does not aggregate return values from the callbacks; it is designed for executing logic that produces observable side effects.