Use function as a parameter and preserve types

Hey guys, i'm trying to figure out a way to do the following I have and interface with some functions/promises
interface SomeInterface {
foo(...args): Promise<void>,
bar(...args): void
...
}
interface SomeInterface {
foo(...args): Promise<void>,
bar(...args): void
...
}
Is there a way the create a function wrapper with the parameter is a function of SomeInterface and the return type is the parameter of this function. Something like this:
function wrapper(func: FuncOfSomeInterface): ResultOfFunc {
...do smth here

const result = func(...args)

...do smth here

return result
}
function wrapper(func: FuncOfSomeInterface): ResultOfFunc {
...do smth here

const result = func(...args)

...do smth here

return result
}
Then i would do this: wrapper(foo(...args)) ✅ wrapper(wrong(...args)) ❌ I still a noob in typescript, so i'd be happy if someone help me ❤️
14 Replies
Sybatron
Sybatron2y ago
interface SomeInterface {
foo(arg1: string, arg2: number): Promise<void>;
bar(arg1: string): void;
}

function wrapper(func: SomeInterface["foo"]) {
//somthing
const result = func("something", 1);

//more something

return result;
}
interface SomeInterface {
foo(arg1: string, arg2: number): Promise<void>;
bar(arg1: string): void;
}

function wrapper(func: SomeInterface["foo"]) {
//somthing
const result = func("something", 1);

//more something

return result;
}
is this what you try to achieve? if you require func of type[X] from interface Y without forcing some output type in the wrapper it will automatically get the func return type in this case also wrong(...args) it will give you syntax error for sure if you want funcX and suddenly put type of funcY in the wrapper
Costa.
Costa.OP2y ago
Kinda yes, but it could be any function from SomeInterface I was thinking about some generics approach
Sybatron
Sybatron2y ago
interface SomeInterface<T> {
foo(arg1: string, arg2: number): Promise<T>;
bar(arg1: string): void;
}

interface OtherInterface {
foo(arg1: string, arg2: number): Promise<string>;
}

const fn: OtherInterface["foo"] = (arg1: string, arg2: number) => {
throw new Error("Function not implemented.");
};

function wrapper(func: SomeInterface<string>["foo"]) {
//somthing
const result = func("something", 1);

//more something

return result;
}

const res = wrapper(fn);
interface SomeInterface<T> {
foo(arg1: string, arg2: number): Promise<T>;
bar(arg1: string): void;
}

interface OtherInterface {
foo(arg1: string, arg2: number): Promise<string>;
}

const fn: OtherInterface["foo"] = (arg1: string, arg2: number) => {
throw new Error("Function not implemented.");
};

function wrapper(func: SomeInterface<string>["foo"]) {
//somthing
const result = func("something", 1);

//more something

return result;
}

const res = wrapper(fn);
something like this? the moment you give it some other function from whenever that doesn't satisfies the type needed in the generic one, you gonna get error syntax error
Costa.
Costa.OP2y ago
Hey! thank you for helping me deranged . I think i'm not being precise... Lets say something like:
interface SomeInterface<T> {
foo(arg1: string, arg2: number): Promise<T>;
bar(arg1: string): void;
}

interface OtherInterface {
foo(arg1: string, arg2: number): Promise<string>;
bar(arg1: string): void;
}

const fn: OtherInterface["bar"] = (arg1: string) => {
throw new Error("Function not implemented.");
};

function wrapper(func: SomeInterface<string>["foo"]) {
//somthing
const result = func("something", 1);

//more something

return result;
}

const res = wrapper(fn);
interface SomeInterface<T> {
foo(arg1: string, arg2: number): Promise<T>;
bar(arg1: string): void;
}

interface OtherInterface {
foo(arg1: string, arg2: number): Promise<string>;
bar(arg1: string): void;
}

const fn: OtherInterface["bar"] = (arg1: string) => {
throw new Error("Function not implemented.");
};

function wrapper(func: SomeInterface<string>["foo"]) {
//somthing
const result = func("something", 1);

//more something

return result;
}

const res = wrapper(fn);
This will not work, cuz wrapper only accepts foo". it shoudl accept all functions from SomeInteface and it can not be hardcoded. The interface have more than 30 methods... I think its a really tricky thing I thought about getting the keys from SomeInterface but its does not work...
Sybatron
Sybatron2y ago
i think you may need someone more experienced for this cuz i don't think it's possible to forced bar into foo they are 2 different types returning 2 different things and accepting 2 different things bar doesn't satisfy foo and the other way arround
Costa.
Costa.OP2y ago
@ mattpocock 🙏 Im not thinking about forcing bar into foo, its more like calling bar or foo get the return types calling a wrapper The wrapper has the function of logging into some file And dispatch some state actions
const result = wrapper(foo("123", 12))
const result = wrapper(foo("123", 12))
Sybatron
Sybatron2y ago
the wrapper should be generic too, so you can have generic args, generic type of func and generic output as they input different count of args with different types and return different type
Costa.
Costa.OP2y ago
function wrapper<T>(func: (args: any) => T, args: any) {
... log
const result = func(args)
... dispatch
}
function wrapper<T>(func: (args: any) => T, args: any) {
... log
const result = func(args)
... dispatch
}
smth like this right? However, how can i force func to be in SomeInterface?
Sybatron
Sybatron2y ago
that's what i'm trying to say you have to have constraint on some part of your function you can't be random count of args for each function with random types return random type you lose all type-safety and just type javascript at this point For what you really want i think function overloading is better suited
Costa.
Costa.OP2y ago
🧐
del.hydraz
del.hydraz2y ago
Throwing my hat in the ring here too, this is the best I could get:
interface FunctionList {
foo(arg1: string, arg2: number): string;
bar(arg1: string): string;
memes(): number;
}

const sayHello = (name: string) => `Hello there, ${name}`;
const wishHappyBirthday = (name: string, age: number) => {
return `Happy ${age}th birthday, ${name}`;
};

const badFunction = (bool: boolean) => {
console.log(bool);
};

const wrapper = <F extends FunctionList[keyof FunctionList]>(fn: F) => {
return (...args: Parameters<F>): ReturnType<F> => {
// execute some logic here

const result = fn(...args) as ReturnType<F>; // ts(2556) on args spreading

// execute more logic

return result;
};
};

const res = wrapper(sayHello)("Jenny");

const otherRes = wrapper(wishHappyBirthday)("Jenny", 35);

// @ts-expect-error badFunction does not match any signature in FunctionList
const badRes = wrapper(badFunction);
interface FunctionList {
foo(arg1: string, arg2: number): string;
bar(arg1: string): string;
memes(): number;
}

const sayHello = (name: string) => `Hello there, ${name}`;
const wishHappyBirthday = (name: string, age: number) => {
return `Happy ${age}th birthday, ${name}`;
};

const badFunction = (bool: boolean) => {
console.log(bool);
};

const wrapper = <F extends FunctionList[keyof FunctionList]>(fn: F) => {
return (...args: Parameters<F>): ReturnType<F> => {
// execute some logic here

const result = fn(...args) as ReturnType<F>; // ts(2556) on args spreading

// execute more logic

return result;
};
};

const res = wrapper(sayHello)("Jenny");

const otherRes = wrapper(wishHappyBirthday)("Jenny", 35);

// @ts-expect-error badFunction does not match any signature in FunctionList
const badRes = wrapper(badFunction);
I think your best bet is to refactor the arguments for each function in FunctionList into an object and then infer the type of the object from there, that way you don't have to mess with arrays and tuples and stuff. There may be a way to pass the key of the function you want, then pass the function itself in. That way, you could get the parameters and the return type directly from the object, not from the function, although I don't think that's very type safe. Hope this helps
Sybatron
Sybatron2y ago
but wont that mean they will have to be sure they can do the same logic and operations with all result types and all arguments types
del.hydraz
del.hydraz2y ago
Not necessarily, if I'm understanding what you're saying correctly. I'm not entirely sure what the use case is, but it almost looks like OP is using an interface like an enum with data attached to it, similar to Rust's enums. I'm actually working on a series of packages that solves this exact use case (assuming this is OP's use case) and in my code I convert the interface into a discriminated union so I can properly type the input data. I'm using a slightly different method here to keep things more fine-tuned to the question's code, and really the only issue I came across was TS not understanding that Parameters<F> will always return a tuple
Want results from more Discord servers?
Add your server