1

In the following code, I try to create a type-safe function that access the data property of the given input. My goal is to avoid repeating the access to data everywhere, and for this to be worth it I would like to keep boilerplate to a minimum (e.g. I would like TypeScript to infer as many type as possible).

interface User {
    name: string
}

interface HttpResponse<T> {
    data: T
    status: number
}

type HttpPromise<T> = Promise<HttpResponse<T>>

function fetch<T>(url: string): HttpPromise<T> {
    return
}

function getData<T>(response: HttpResponse<T>): T {
    return response.data
}

// Doesn't work: 'Promise<{}>' is not assignable to type 'Promise<User>'.
function fetchUser(): Promise<User> {
    return fetch('https://...').then(getData)
}

I know how to make it work by explicitly stating more types, but I don't know why this doesn't work already. In particular:

  1. Where does the {} type come from (in Promise<{}>)?
  2. Why can't TypeScript infer the type of the expression properly, based on the return type?

Link to TypeScript's playground with the code.

2 Answers 2

2

Let's start with solution:

function fetchUser(): Promise<User> {
    return fetch<User>('https://...').then(getData)
}

In order for type inference to work we need to give TypeScript a starting point, which in this case is the function with which we initiate the execution. If we let fetch method generic T parameter to have a default value (which is {}) then TypeScript will use that type to determine all connected function calls, basically making then and getData to inherit that default type. In the end, when TypeScript gets to the point when it have to verify type correctness between last function signature (getData) and outer fetchUser signature it will discover conflict, because getData is assumed to work with {} type and fetchUser states explicitly that it works with User type.

You can compare how TypeScript will work out returned type when you omit completely the type returned by fetchUser function:

function fetchUser() {
    return fetch<User>('https://...').then(getData)
}

If you hover over fetchUser function you will see that TypeScript inferred the User type from inner fetch function signature and its still Promise< User>.

Sign up to request clarification or add additional context in comments.

Comments

1

For your first question, the reason why TypeScript is complaining about the type being Promise<{}> is that, if you don't specify a type for a generic function that returns a promise, it's by default Promise<{}>.

With this in mind, by calling fetch('http://...'), without providing the type parameter, TypeScript will infer that it's Promise<HttpResponse<{}>>.

I assume you were able to make it work by specifying the User type when calling fetch in fetchUser, like so:

return fetch<User>('https://...').then(getData);

The inference that you're looking for would've worked if you were returning the call to fetch directly. Something like:

async function getValueAsync<T>(): Promise<T> {
    return null;
}

function testGetValueAsync(): Promise<User> {
    return getValueAsync();
}

However, since you're chaining off of that call, there is nothing in the fetch call that tells TypeScript what type it's returning.

Therefore, the minimal change you need to make is to specify the User type when calling fetch

return fetch<User>('https://...').then(getData);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.