0

How can I refer to a specific function argument right inside the function signature?

enter image description here

The image above is a fake just for demonstration! I provide name and arg and need to retrieve some type info for arg by stuffing name into another function. But I don't know how to refer to name inside the function signature.


Context:

I'm working on a simple event manager. You can define event-names, assign callbacks to those and fire events. Callbacks may have an arbitrary number of arguments.

(The example below is very reduced, the real implementation is more complex, e.g. events can have multiple callbacks etc)

type CallbackType = (...args: any[]) => void;

const eventRegistry: Map<string, CallbackType> = new Map();

const registerEvent = (name: string, callback: CallbackType): void => {
        eventRegistry.set(name, callback);
}

const fireEvent = (name: string, ...args: any[]): void => {
    eventRegistry.get(name)!(...args);
}

some example callbacks

const foo = (input: number): void => {
    console.log("foo here:", input);
}

const bar = (input1: boolean, input2: string): void => {
    console.log("bar here:", input1, input2);
}

event registration and event firing

registerEvent("someEventName", foo);
registerEvent("anotherEventName", bar);

fireEvent("someEventName", 42);
fireEvent("anotherEventName", true, "Hello World");

Here's a link to a typescript playground.


The implementation above is not type-safe. You don't get any hints in fireEvent what arguments the callbacks expect.

So I tried using typescripts Parameters utility type:

const fireEvent = (name: string, ...args: Parameters<typeof eventRegistry.get(name)>): void => {
    eventRegistry.get(name)!(...args);
}

But that got me nowhere. To correctly type args I first need to get the callback assigned to the event but I don't know how to refer to name.

cl10k
  • 912
  • 1
  • 7
  • 15
  • You are expecting compile-time auto completion of the `...args` parameter. But which events are registered to which `name` is currently only known at runtime. You should create a compile-time known data structure which has information about the relation between specific events and event names. Then you can solve this problem with generics. – Tobias S. Jul 18 '22 at 12:06

1 Answers1

0

You can use Parameters to get the arguments of a function, and you can use spread to spread the arguments back to a function. You can even use generic type parameters and index types to get the type of the function from an object typoe:

type EventRegistry = {
    someEventName: typeof foo
    anotherEventName: typeof bar
}

const eventRegistry: Map<string, CallbackType> = new Map();
const fireEvent = <K extends keyof EventRegistry>(name: K, ...args: Parameters<EventRegistry[K]>): void => {
    eventRegistry.get(name)!(...args);
}

fireEvent("anotherEventName", true, "") // ✅
fireEvent("anotherEventName", true, 0) // ❌

Playground Link

Your real problem here is that registerEvent does not in any way leave a mark on the type of fireEvent. This means that fireEvent does not know what events were registered, if we want this information at compile time, we need to flow it from the register calls to the fireEvent calls.

Here is my stab at it, not a definitive solution, but a starting point:

class EventRegistry<T extends  Record<string, CallbackType> = {}>{
    eventRegistry: Map<string, CallbackType> = new Map();

    registerEvent<K extends string, C extends CallbackType>(name: K, callback: C):EventRegistry<T & Record<K, C>>   {
        this.eventRegistry.set(name, callback);
        return this;
    }

    fireEvent<K extends keyof T & string>(name: K, ...args: Parameters<T[K]>): void  {
        this.eventRegistry.get(name)!(...args);
    
}
const reg = new EventRegistry()
    .registerEvent("someEventName", foo)
    .registerEvent("anotherEventName", bar);

reg.fireEvent("someEventName", 42); // ✅
reg.fireEvent("anotherEventName", true, "Hello World"); // ✅
reg.fireEvent("anotherEventName", true, 0); // ❌

Playground Link

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357