Jespertheend
Jespertheend

Reputation: 2250

How to distinguish `(() => any) | undefined` from just `() => any` in TypeScript?

I have two types: (() => any) | undefined and () => any. I would like extract the return type of the function but only if the value is definitely not undefined.

I have tried the following:

type IsUndefined<T> = T extends (() => any) ? "no" : "yes";

But this resolves to "yes" | "no" when the type is not undefined. I want to detect the difference between these types without creating a union.

Please see this playground link for an example.

That is the short story, the long story is that I have a struct like the following:

type MyStruct = {
    optionalHook?: ((...args: any) => any) | undefined,
    requiredHook: (...args: any) => any,
}

I would like to extract the return type of the optional hook, but only when it exists. I would like to extract the return type of the required hook otherwise.

See this playground link for a more comprehensive example.

Upvotes: 2

Views: 262

Answers (2)

Guerric P
Guerric P

Reputation: 31805

Not sure why the absence of optionalHook doesn't infer T to undefined in createStruct but I've managed to get the desired result by adding default generic parameters:

// Create the generic struct
type MyStruct<T extends ((...args: any) => any) | undefined, U extends (...args: any) => any> = {
    optionalHook?: T,
    requiredHook: U,
}

// Utility function for easily creating stucts with the correct type.
// Without this I'd have to type the generic arguments manually for every struct instance.
function createStruct<T extends ((...args: any) => any) | undefined = undefined, U extends (...args: any) => any = (...args: any) => any>(struct: MyStruct<T, U>) {
    return struct;
}

const withOptional = createStruct({
    optionalHook: () => 5,
    requiredHook: () => "hello",
});

const withoutOptional = createStruct({
    requiredHook: () => true,
});


// The function for extracting the return type
type getReturnType<T> =
    T extends MyStruct<infer O, infer R> ?
        O extends (...args: any) => infer OR ?
            OR :
            R extends (...args: any) => infer RR ? RR : never :
        never;


type ResultWithOptional = getReturnType<typeof withOptional>;
type ResultWithoutOptional = getReturnType<typeof withoutOptional>;

TypeScript playground

Explicitly set optionalHook to undefined also works:

TypeScript playground

Upvotes: 2

Szaman
Szaman

Reputation: 2388

You can use a custom util like this:

export const isSet = <T>(value: T | null | undefined): value is T => {
  return value != null;
};

And then use it:

let something; // (() => any) | undefined
if (isSet(something)) {
  doSomething(something); // () => any
}

And if you need the opposite (i.e. check that a type is undefined), then you don't even need a custom util, you can use lodash — either isUndefined or isNil (= is undefined or null).

Upvotes: 1

Related Questions