Wesley Wiser
Wesley Wiser

Reputation: 9851

How to have an optional argument type in a generic function type?

I have a generic function type that looks like this:

type Test<TArg> = (arg: TArg) => void;

Most of the time, the argument is used by functions conforming to this type:

let x1: Test<number> = (n) => { console.log(n) };
let x2: Test<string> = (s) => { alert(s) };

Sometimes though, I have a function that doesn't take any arguments:

let y1: Test<undefined> = () => { };

I'd like to be able to invoke this function without providing any arguments at the callsite like this:

y1();

But no matter what types I plug in for TArg, I can't seem to do that. Is there anyway to do this without making the parameter optional in the definition of Test?

Here's what I've tried:

type Test<TArg> = (arg: TArg) => void;

let x: Test<number> = (n) => { console.log(n) };
let y1: Test<never> = () => { };
let y2: Test<any> = () => { };
let y3: Test<undefined> = () => { };
let y4: Test<null> = () => { };

x(1);
y1(); //Expected 1 arguments, but got 0.
y2(); //Expected 1 arguments, but got 0.
y3(); //Expected 1 arguments, but got 0.
y4(); //Expected 1 arguments, but got 0.

Upvotes: 3

Views: 992

Answers (1)

Greg Rozmarynowycz
Greg Rozmarynowycz

Reputation: 2085

If having an execution wrapper is acceptable, I've come up with a solution that handles either function length (0 or 1), and is type safe for the argument:

type Test<T> = (arg?: T) => void;

class Wrapper<T, F extends Test<T>> {
    public execute: F;

    public static create<T1, F1 extends Test<T1>>(method: F1): Wrapper<T1, F1> {
        return new Wrapper(method);
    }

    constructor(method: F) {
        this.execute = method;
    }
}

const func1 = Wrapper.create((a: string) => console.log(a));
const func2 = Wrapper.create(() => { });

func1.execute('3');
func2.execute();

func1.execute(5); // 5 is not assignable to type "string"
func1.execute(); // expected 1 argument
func2.execute(5); // expected 0 arguments

Note if you wanted to expand this beyond 1 parameter you could add additional generic types but you'd have to manually define each one and carry it through the wrapper class, but it would work.

EDIT removed original optional parameter solution

Upvotes: 2

Related Questions