The Generic Function's Return Type in Typescript

The latest addition of ReturnType in TypeScript 2.8 is a highly valuable feature that enables you to capture the return type of a specific function.

function foo(e: number): number {
    return e;
}

type fooReturn = ReturnType<typeof foo>; // number

Nevertheless, I am encountering difficulties when trying to utilize it within the scope of generic functions.

function foo<T>(e: T): T {
    return e;
}

type fooReturn = ReturnType<typeof foo>; // type fooReturn = {}

type fooReturn = ReturnType<typeof foo<number>>; // syntax error

type fooReturn = ReturnType<(typeof foo)<number>>; // syntax error

Is there a method to extract the return type of a generic function given certain type parameters?

Answer №1

In the past, achieving this in a completely generic manner was not possible, but it will become available in Typescript version 4.7. This concept is known as an "Instantiation Expression". You can find more information in the related PR here. Here is an excerpt from the description:

function createContainer<T>(value: T) {
  return { value };
};

const createStringContainer = createContainer<string>;  // (value: string) => { value: string }
const stringContainer = createStringContainer('abc');  // { value: string }

const ErrorObjectMap = Map<string, Error>;  // new () => Map<string, Error>
const errorObjectMap = new ErrorObjectMap();  // Map<string, Error>

...

A particularly valuable technique is to generate universal type definitions for instances of typeof that use type parameters in type instantiation expressions:

type ContainerFunction<T> = typeof createContainer<T>;  // (value: T) => { value: T }
type Container<T> = ReturnType<typeof createContainer<T>>;  // { value: T }
type StringContainer = Box<string>;  // { value: string }

Answer №2

Here is a method I developed to extract un-exported internal types from imported libraries, such as knex:

// The foo function is an imported function that cannot be controlled
function foo<T>(e: T): InternalType<T> {
    return e;
}

class Wrapper<T> {
  // The wrapped function has no explicit return type, allowing for inference
  wrapped(e: T) {
    return foo<T>(e)
  }
}

type FooInternalType<T> = ReturnType<Wrapper<T>['wrapped']>
type Y = FooInternalType<number>
// Y === InternalType<number>

Answer №3

One way to obtain a specialized generic type is by utilizing a dummy function for encapsulation.

const wrapFunction = () => functionName<dataType>()
type ResultType = ReturnType<typeof wrapFunction>

Here is a more intricate example:

function generateList<T>(): {
  items: T[],
  add: (item: T) => void,
  remove: (item: T) => void,
  // ...and so on
}
const wrappedListGenerator = () => generateList<number>()
type CustomList = ReturnType<typeof wrappedListGenerator>
// CustomList = {items: number[], add: (item: number) => void, remove: (item: number) => void, ...and so on}

Answer №4

In my quest to find a solution, I stumbled upon a clever and straightforward method for achieving this task if you are able to modify the function definition. In my scenario, the challenge was utilizing the typescript type Parameters with a generic function. Specifically, I attempted

Parameters<typeof foo<T>>
, only to discover that it did not yield the desired outcome. The most effective approach, in this case, is to alter the function definition to an interface function definition. This adjustment also seamlessly integrates with the typescript type ReturnType.

Allow me to illustrate this concept using an example based on the issue raised by the original poster:

function foo<T>(e: T): T {
   return e;
}

type fooReturn = ReturnType<typeof foo<number>>; // Unfortunately, an error is thrown

// However, by redefining your function as an interface:

interface foo<T>{
   (e: T): T
}

type fooReturn = ReturnType<foo<number>> // Success! It outputs 'number'
type fooParams = Parameters<foo<string>> // Works perfectly! Result is [string]

// Additionally, you can utilize the interface like so:
const myfoo: foo<number> = (asd: number) => {
    return asd;
};

myfoo(7);

Answer №5

I have discovered a solution that may meet your requirements :)

To define function arguments and return type, utilize an interface

interface Bar<X, Y> {
  (x: X, y: Y): [X, Y]
}

Implement the function in this manner using Parameters and ReturnType

function bar<X, Y>(...[x, y]: Parameters<Bar<X, Y>>): ReturnType<Bar<X, Y>> {
  return [x, y]; // [X, Y]
}

Invoke the function normally, or retrieve the return type using ReturnType

bar(2, 'b') // [number, string]
type Example = ReturnType<Bar<number, number>> // [number, number]

Answer №6

const wrapperFoo = (process.env.NODE_ENV === 'typescript_helper' ? foo<number>(1) : undefined)!
type Return = typeof wrapperFoo

TypeScript Playground

Here's a scenario where a default type is used but not exported.

// Default type not accessible
interface Unaccessible1 {
    z: number
    x: string
}

function foo1<T extends Unaccessible1>(e: T): T {
    return e;
}

const wrapperFoo1 = (process.env.NODE_ENV === 'typescript_helper' ? foo1.apply(0, 0 as any) : undefined)!
type ReturnFoo1 = typeof wrapperFoo1 // Unaccessible1

interface Unaccessible2 {
    y: number
    c: string
}

function foo2<T extends Unaccessible2>(e: T, arg2: number, arg3: string, arg4: Function): T {
    return e;
}

const wrapperFoo2 = (process.env.NODE_ENV === 'typescript_helper' ? foo2.apply(0, 0 as any) : undefined)!
type ReturnFoo2 = typeof wrapperFoo2 // Unaccessible2

TypeScript Playground

Answer №7

The TypeScript compiler does not interpret typeof foo as a generic type, potentially indicating a bug in the compiler.

Nevertheless, TypeScript offers callable interfaces that can be utilized in a generic manner without any difficulties. By creating a callable interface that mirrors the function's signature, you can create your own version of ReturnType as demonstrated below:

function foo<T>(x: T): T {
  return x;
}


interface Callable<R> {
  (...args: any[]): R;
}

type GenericReturnType<R, X> = X extends Callable<R> ? R : never;

type N = GenericReturnType<number, typeof foo>; // number

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to detect changes in a Service variable within an Angular component?

One of my components contains a button that activates the showSummary() function when clicked, which then calls a service named Appraisal-summary.service.ts that includes a method called calc(). showSummary(appraisal) { this.summaryService.calc(appraisal ...

Using regular expressions, you can locate and replace the second-to-last instance of a dot character in an email address

I'm looking to replace the second last occurrence of a character in a given string. The length of the strings may vary but the delimiter is always the same. Here are some examples along with my attempted solutions: Input 1: james.sam.uri.stackoverflo ...

The Redux Toolkit slice and TypeScript were not in agreement: it was expecting 0 arguments, but received

Recently, I encountered an issue with my slice code: const investment = createSlice({ name: 'investments', initialState, reducers: { getInvestmentsRequest(state) { state.investments.status = RequestStatuses.loading; }, } }) ...

Hidden input fields do not get populated by Angular submit prior to submission

I am in the process of implementing a login feature in Angular that supports multiple providers. However, I have encountered an issue with submitting the form as the loginProvider value is not being sent to the server. Below is the structure of my form: &l ...

How can we track and record NaN values in JavaScript/TypeScript as they occur in real-time?

Is there a reliable method to identify and prevent NaN values during runtime, throughout all areas of the application where they might arise? A) Are there effective linting tools available to alert about possible occurrences of NaN values within specific ...

The data type 'string' cannot be assigned to the type 'SystemStyleObject | undefined' in the context of Next.js and Chakra UI

I am currently working on a project that involves Next.js, TypeScript, and Chakra UI. While configuring Button themes in the button.ts file, I encountered an error related to the baseStyle object for properties like borderRadius, color, and border: Type & ...

Step-by-step guide on filtering an array of objects using Vuejs and TypeScript

For this particular project, I am utilizing Vuejs Typescript with a data structure that looks like this: ["order": { "id":1, "created_at":"2019-12-06T10:22:17Z", "status":"open", ...

What is the method for adding pages to the ion-nav component in Ionic with Angular?

How can I implement a UINavigationController-like functionality in iOS using an ion-nav element? The example provided here is in Javascript, but I need assistance with implementing it in Angular. Specifically, I'm unsure of how to programmatically add ...

nodemon failing to automatically refresh files in typescript projects

I am currently developing an app using NodeJs, express, typescript, and nodemon. However, I am encountering an issue where the page does not refresh when I make changes to the ts files. Is there a way for me to automatically compile the ts files to js an ...

How can one pass a generic tuple as an argument and then return a generic that holds the specific types within the tuple?

With typescript 4 now released, I was hoping things would be easier but I still haven't figured out how to achieve this. My goal is to create a function that accepts a tuple containing a specific Generic and returns a Generic containing the values. i ...

Utilizing NGRX reducers with a common state object

Looking for a solution with two reducers: export function reducer1(state: State = initialState,: Actions1.Actions1); export function reducer2(state: State = initialState,: Actions2.Actions1); What I want is for both reducers to affect the same state objec ...

Reasons why a functional component may not trigger a rerender after a state change using useReducer()

When using react Hooks, specifically useReducer, I found that although the state changes, the functional component does not rerender. Additionally, when trying to open the drawer by pressing a button in the menu, even though the state changes the drawer re ...

Using TypeORM: Implementing a @JoinTable with three columns

Seeking assistance with TypeORM and the @JoinTable and @RelationId Decorators. Any help answering my question, providing a hint, or ideally solving my issue would be greatly appreciated. I am utilizing NestJS with TypeORM to create a private API for shari ...

You can't observe the behavior of simulated functions in a class with a manually created mock

Kindly note that I have set up a comprehensive Github repository where you can download and explore the content yourself here I am currently working on mocking a non-default exported class within a module using a manual mock placed in the folder __mocks__ ...

How can you check the status of a user in a Guild using Discord JS?

Is there a way to retrieve the online status of any user in a guild where the bot is present? Although I can currently access the online status of the message author, I would like to be able to retrieve the online status of any user by using the following ...

Tips on preventing image previews from consuming too much text data while updating a database in Angular 12 using Material UI for image uploads within a FormGroup object

Currently working with Angular 12 and Angular Material for image uploads with preview. I have a formgroup object below, but I'm running into issues with the 197kb image preview text being inserted into the database. Despite trying setValue/patchValue/ ...

Transform the subscription into a string

When I use the http method to retrieve a single user, the output logged in the console looks like this: this.usersService.getOneUser().subscribe(data => { console.log(data) }); email: "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data- ...

Ensuring the type of a specific key in an object

Seeking a more stringent approach regarding object keys in TypeScript. type UnionType = '1' | '2' | '3' type TypeGuardedObject = { [key in UnionType]: number } const exampleObject: TypeGuardedObject = { '1': 1, ...

Combining information from two different sources to create a more comprehensive dataset

Two get requests are returning the following data: [{ id: 1, hId: 2 }, { id: 6, hId: 1 }] The second request returns: [{ id: 1, name: 'Bob' }, { id: 2, name: 'Billy' }, { id: 6, name: 'John' }] The object ...

What is the proper way to implement an if-else statement within objects?

Is there a way to convert the code below into an object structure so I can access nodID and xID keys from different files? The issue lies in the if statement within the code. My idea is to use export const testConfig = {} and import testConfig into any fil ...