DEV Community

A. Sharif
A. Sharif

Posted on • Edited on

Notes on TypeScript: ReturnType

Introduction

These notes should help in better understanding TypeScript and might be helpful when needing to lookup up how leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.

ReturnType

In the following we will talk about the ReturnType.
To better understand ReturnType, we will build examples a long the way, that should display where using this conditional type might be useful.

Before we go into more depth, let's write a basic example to demonstrate how ReturnType functions.
We have a getInt function that expects an input of type string and parses the integer value of given input.

function getInt(a: string) {
  return parseInt(a);
}

type A = ReturnType<typeof getInt>; // => number
Enter fullscreen mode Exit fullscreen mode

Using typeof, we are able to able to get hold of the type signature of getInt, which in this case would mean (a: string) => number. ReturnType accepts a function and returns the return type that the function would return when being invoked. number in our getInt example.

Taking this approach has one main advantage, we don't need to keep the return types in sync with our function definition.

Advanced

Now that we have seen what ReturnType does and how we can leverage this type in it's most basic form, let's try to get a deeper understanding and see more advanced cases.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
Enter fullscreen mode Exit fullscreen mode

Above, is the ReturnType implementation. It tries to infer the return value and either returns the inferred type or any.

In our next example, we want to get a hold of the return type of a function that creates a User.

let id = 0;
function createUser(name: string, position: string) {
  return {
    id: id++,
    name,
    position,
    createdAt: new Date()
  };
}

type User = ReturnType<typeof createUser>;
// => {id: number, name: string, position: string, createdAt: Date}
Enter fullscreen mode Exit fullscreen mode

Again, ReturnTypes can return the created User type. This means we don't have to keep our types in sync with our function declaration.
Here are more examples using ReturnType.

type A = ReturnType<any>; // => any
type B = ReturnType<never>; // => never
type C = ReturnType<() => string>; //=> string
Enter fullscreen mode Exit fullscreen mode

Our next example is trying to infer the None type, where we define a constant for None.

const None = "None";

function none() {
  return {type: None}
}

type NoneType = ReturnType<typeof none>; // => {type: string}
Enter fullscreen mode Exit fullscreen mode

Interestingly type NoneType is defined as {type: string} but should actually be {type: "None"}.
To enable TypeScript to infer the correct type, we need to be explicit about the None constant in this case.

const None = "None";

function none() {
  return {type: None as typeof None}
}

type NoneType = ReturnType<typeof none>; // => {type: "None"}
Enter fullscreen mode Exit fullscreen mode

By explicitly defining the None type, ReturnType can infer the correct type.
We need to keep this in mind in these specific cases.

Finally, let's see how we might be able to infer the return type of a generic function.

function identity<T>(a: T) : T {
  return a;
}
Enter fullscreen mode Exit fullscreen mode

Let's see what ReturnType can infer:

type IdentityType = ReturnType<typeof identity>; // => {}
type IdentityType = ReturnType<typeof identity<number>>; // => syntax error!
Enter fullscreen mode Exit fullscreen mode

So we can't infer identity.
How do we infer the return type of a generic function then?

We need to implement our own return type as suggested here.

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

type GenericReturnType<ReturnType, F> = F extends Callable<ReturnType>
  ? ReturnType
  : never;
Enter fullscreen mode Exit fullscreen mode

Now using our GenericReturnType we can infer the correct return type:

type IdentityType = GenericReturnType<string, typeof identity>; // => string
type IdentityType = GenericReturnType<number, typeof identity>; // => number
Enter fullscreen mode Exit fullscreen mode

When it comes to generic functions we might be dealing with suboptimal conditions currently. The above GenericReturnType will work with the identity function but might break when dealing with more complex generic functions.

On a side note, the above GenericReturnType works due to the fact that interfaces in TypeScript can describe function types. Take a look at the following example:

interface GetInt {
    (a: string): number;
}

const getInt: GetInt = (a) => parseInt(a);

type A = ReturnType<typeof getInt>; // => number
Enter fullscreen mode Exit fullscreen mode

We should have a better understanding of how ReturnType functions now and where we might gain some benefits when using it in our application.

If you have any questions or feedback please leave a comment here or connect via Twitter: A. Sharif

Top comments (1)

Collapse
 
rmrfetc profile image
Rob • Edited

Apparently we can't infer the return type of a generic function based on a generic input. One of the guys in the gitter TypeScript channel gave me this example explanation.

function identity<T>(a: T) : T {
  return a;
}
interface Callable<ReturnType> {
  (...args: any[]): ReturnType;
}
type GenericReturnType<ReturnType, F> = F extends Callable<ReturnType>
  ? ReturnType
  : never;

type IdentityType = GenericReturnType<string, typeof identity>; // => string

^ example however does not actually work how it's explained...

function identity<T>(a: T) : Promise<T> {
  return Promise.resolve(a);
}
interface Callable<ReturnType> {
  (...args: any[]): ReturnType;
}
type GenericReturnType<ReturnType, F> = F extends Callable<ReturnType>
  ? ReturnType
  : never;

type IdentityType = GenericReturnType<string, typeof identity>; // never