Question:
TypeScript: Define a type that refers type parameter from another line?

Problem

Using the Promise type as an example:


interface Promise<T> {

  then<TResult1 = T, TResult2 = never>(

    onfulfilled?:

      | ((value: T) => TResult1 | PromiseLike<TResult1>)

      | undefined

      | null,

    onrejected?:

      | ((reason: any) => TResult2 | PromiseLike<TResult2>)

      | undefined

      | null

  ): Promise<TResult1 | TResult2>;


  catch<TResult = never>(

    onrejected?:

      | ((reason: any) => TResult | PromiseLike<TResult>)

      | undefined

      | null

  ): Promise<T | TResult>;

}


This code might look cleaner if the onfulfilled and/or onrejected types were defined on a separate line. This doesn't compile, but here's what it might look like:


interface Promise<T> {

  type OnFulfilled = 

      | ((value: T) => TResult1 | PromiseLike<TResult1>)

      | undefined

      | null

  type OnRejected =

      | ((reason: any) => TResult2 | PromiseLike<TResult2>)

      | undefined

      | null


  then<TResult1 = T, TResult2 = never>(

    onfulfilled?: OnFulfilled,

    onrejected?: OnRejected

  ): Promise<TResult1 | TResult2>;


  catch<TResult = never>(

    onrejected?: OnRejected

  ): Promise<T | TResult>;

}


Is it possible to write types like this, to improve type readability?


Solution

It is not currently possible to create "local" or "inline" >type aliases this way. There are several open feature requests asking for support for this; >microsoft/TypeScript#41470 proposes the same syntax you're using, while >microsoft/TypeScript#23188 proposes a different syntax which would have the same effect. Until and unless such features are implemented, you'll have to work around it.


The closest you can get with TypeScript as of TS5.2 is to just create regular non-local, non-inline, separate type aliases, and give them >generic parameters to pass types which are now out of scope. Like this, for example:


type OnFulfilled<T, R> =

    ((value: T) => R | PromiseLike<R>) | undefined | null;


type OnRejected<R> =

    ((reason: any) => R | PromiseLike<R>) | undefined | null;


interface Promise<T> {

    then<R1 = T, R2 = never>(

        onfulfilled?: OnFulfilled<T, R1>,

        onrejected?: OnRejected<R2>

    ): Promise<R1 | R2>;

    catch<R = never>(

        onrejected?: OnRejected<R>

    ): Promise<T | R>;

}


It's not the same, especially since you are now introducing new types in the same namespace. For ambient types like Promise<T>, doing this pollutes the global namespace and possibly interferes with user-defined types. But depending on the use case it might be a viable solution.


>Playground link to code


Suggested blogs:

>Why Typescript does not allow a union type in an array?

>Narrow SomeType vs SomeType[]

>Create a function that convert a dynamic Json to a formula in Typescript

>How to destroy a custom object within the use Effect hook in TypeScript?

>How to type the values of an object into a spreadsheet in TypeScript?

>Type key of record in a self-referential manner in TypeScript?

>How to get the last cell with data for a given column in TypeScript?

>Ignore requests by interceptors based on request content in TypeScript?

>Create data with Typescript Sequelize model without passing in an id?

>How to delete duplicate names from Array in Typescript?


Nisha Patel

Nisha Patel

Submit
0 Answers