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

The Angular custom modal service is malfunctioning as the component is not getting the necessary updates

As I develop a service in Angular to display components inside a modal, I have encountered an issue. After injecting the component content into the modal and adding it to the page's HTML, the functionality within the component seems to be affected. F ...

The Angular-slickgrid is encountering an issue where it is unable to access the property "hostView" as it

Hey there developers, I've been experimenting with the angular slickgrid library and attempting to incorporate the rowDetailView feature it offers. The issue I'm facing is that while I can expand the view by clicking on it, I'm unable to c ...

Exploring the Concept of Extending Generic Constraints in TypeScript

I want to create a versatile function that can handle sub-types of a base class and return a promise that resolves to an instance of the specified class. The code snippet below demonstrates my objective: class foo {} class bar extends foo {} const someBar ...

What is preventing type guarding in this particular instance for TypeScript?

Here is an example of some code I'm working on: type DataType = { name: string, age: number, } | { xy: [number, number] } function processInput(input: DataType) { if (input.name && input.age) { // Do something } e ...

Establish a public-facing link for a React component

After developing a React component that functions as a chatbot window, I am now looking for a way to make the opening button accessible across various websites and applications. My initial thought was to associate a URL with the button so that it can be ea ...

Ways to leverage Composite API in place of Mixin or Extends functionality

Currently, I am exploring the use of Vue3 for my project. One issue I am facing is that I need to create multiple components (SFC) with similar properties. I want to define these common properties using the composite API across all components, like so: con ...

What's the most efficient way to define the type of an object in TypeScript when one of its properties shares the same name as the type itself?

I'm currently working on processing an event payload where the event field is a string, and the content of data depends on the value of the event field. While I have come up with a representation that functions correctly, I can't help but feel th ...

Embed the value of an array in HTML

Upon running console.log, the data output appears as follows: {TotalPendingResponseCount: 0, TotalRequestSendCount: 1, TotalRequestReceivedCount: 1, TotalRequestRejectCount: 3} To store this information, I am holding it in an array: userData : arrayResp ...

What is the best way to exclude multiple properties from an object in JavaScript?

Having two methods that return Pick<T, K> and Omit<T, K> types where Omit is defined as type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>, I am facing difficulty in removing multiple properties from an object. Th ...

Having trouble utilizing a function with an async onload method within a service in Angular - why does the same function work flawlessly in a component?

I successfully created a component in Angular that can import an Excel file, convert it into an array, and display its content as a table on the page. The current implementation within the component looks like this: data-import.compoent.ts import { Compo ...

Caution: The `id` property did not match. Server: "fc-dom-171" Client: "fc-dom-2" while utilizing FullCalendar in a Next.js environment

Issue Background In my current project, I am utilizing FullCalendar v5.11.0, NextJS v12.0.7, React v17.0.2, and Typescript v4.3.5. To set up a basic calendar based on the FullCalendar documentation, I created a component called Calendar. Inside this comp ...

What is the best way to sequentially invoke methods in Angular?

When initializing in the ngOnInit() method, I need to call several methods synchronously. However, all these methods involve asynchronous calls to an API. The challenge is that certain variables must be set before proceeding with the subsequent methods. Un ...

Unable to locate module 'fs'

Hey there, I'm encountering an issue where the simplest Typescript Node.js setup isn't working for me. The error message I'm getting is TS2307: Cannot find module 'fs'. You can check out the question on Stack Overflow here. I&apos ...

How can I assign a type to an array object by utilizing both the 'Pick' and '&' keywords simultaneously in TypeScript?

As a beginner in TypeScript, I am looking to declare a type for an array object similar to the example below. const temp = [{ id: 0, // number follower_id: 0, // number followee_id: 0, // number created_at: '', // string delete_at: &ap ...

Finding the number of elements in a FirebaseListObservable involves accessing the `length` property

One of the tasks in my Angular 2 application involves retrieving data from a Firebase database and storing it in a FirebaseListObservable. I have a method called getStatus that is supposed to determine the number of elements in this FirebaseListObservable. ...

Is there a way to recursively convert property types from one to another in an object that contains optional properties?

The scenario: I am currently working with MongoDB and a REST API that communicates using JSON. MongoDB uses objects instead of identifiers for documents, but when these objects are stringified (such as in a response body), they get converted into strings. ...

Creating a dynamic list in Typescript from a tuple array without any intersections

const colors = ["red", "blue", "green", "yellow"] as const; const buttonSizes = ["small", "medium", "large"] as const; type ColorType = (typeof colors)[number]; type SizeType = (typeof b ...

Encountering an error with the Next Auth adapter in TypeScript when attempting to modify the default User interface

This is my first time using TypeScript and I am attempting to customize the default User interface for next-auth. I have experimented with the following code: next-auth.d.ts import { User } from "next-auth" import { JWT } from "next-auth/j ...

The module 'module://graphql/language/parser.js' could not be resolved

I'm facing an issue while creating a React Native TypeScript project on Snack Expo. Even though I have added graphql to the package.json and included the types file, I keep getting this error : Device: (1:8434) Unable to resolve module 'module:/ ...

Typescript Declarations for OpenLayers version 6

The package @types/openlayers found at https://www.npmjs.com/package/@types/openlayers only provides type definitions for version 4.6 of OpenLayers. This is clearly stated in the top comment within the file index.d.ts. If types for OpenLayers 6 are not av ...