Beginner's TypeScript Tutorial (18 exercises)
solution

Update Return Type Annotations for Async Functions

There are several solutions here.

Do What TypeScript Suggests

In the starting point of the code, hovering over the error message in VS Code pops up a message:

The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<LukeSkywalker>'?

We get this error because the fetchLukeSkywalker function is async, so the type returned will be a Promise.

Following TypeScript's suggestion to write Promise<LukeSkywalker> will fix this error:

export const fetchLukeSkywalker = async (): Promise<LukeSkywalker> => {

Note that this syntax is similar to the Array<Post> syntax we saw earlier. But unlike arrays, there's only one way to use Promise<T>.

This solution works well, and is probably what I'd recommend.

Type the Fetched Data Response

By default, the data returned from a fetch request will be typed as Any.

Even though we know from the API what our response will contain, TypeScript has no idea. That means that the Any type doesn't give us any autocomplete when we use it.

export const fetchLukeSkywalker = async () => {
  // `data` will be typed as `Any`
  const data = await fetch(

But because we've awaited the fetch, we can type data as LukeSkywalker:

const data: LukeSkywalker = await fetch(

This would give us the autocomplete because it's been properly typed.

Now when we hover over the fetchLukeSkywalker function declaration, we can see that TypeScript has inferred the return type of the function to be Promise<LukeSkywalker> just like we saw in the prior solution.

Cast Data as a Type

In the cases we've looked at so far, we're kind of lying to ourselves a little bit.

TypeScript doesn't know what we're going to return, so we have to tell it. But really, we don't know what the result of our fetch is going to be.

This is where this soltion comes in:

export const fetchLukeSkywalker = async () => {
  const data = await fetch("<https://swapi.dev/api/people/1>").then((res) => {
    return res.json();
  });

  // cast the data to LukeSkywalker
  return data as LukeSkywalker;
};

The return data as LukeSkywalker line casts the fetch response to our LukeSkywalker type.

When working with fetch requests, you should either cast the return data as a type or assign a type to the data when the request is made.

More About Casting

Casting allows us to let anyone become LukeSkywalker:

const matt = {} as LukeSkywalker

Compare the above to if we said matt was assignable to LukeSkywalker:

const matt: LukeSkywalker = {}

This would give us errors because the object doesn't include the appropriate properties. Generally speaking, the assigning syntax is safer than the as syntax, and is probably what you should do in most cases.

Transcript

There are several solutions here. The first one is this. This is the one that TypeScript hints at you to do when you add this return type. The return type of an async function or method must be the global promised T type. Did you mean to write Promise Luke Skywalker? This is really cool.

It's because we've specified this as an async function. What it means is if we were to use this, if we were to say const luke equals fetch Luke Skywalker, this attribute here is not Luke Skywalker. It's going to be a promise that represents Luke Skywalker that will resolve to be Luke Skywalker. We should wrap it in promise here.

This syntax is exactly the same as the array syntax I showed you earlier. There's no other way to represent promises here. This is what we've got. It's pretty good. We got our fetch Luke Skywalker. This is the first solution and it works. This is probably what I recommend, maybe.

Let's look at the third solution first. The third solution is a little bit funky in terms of formatting. If I remove this and I say data is this, data is typed as any. Now, any is a really interesting type in TypeScript because it doesn't give you any autocomplete when you use it. Data dot blah, blah, blah, blah, I'm not getting any of my name, height, mass, hair color stuff.

This data here, because we've awaited this fetch, I can actually say this is Luke Skywalker there using the syntax that we've seen before. Now, my data is typed as Luke Skywalker, so I'll get access to data dot birth year, data dot eye color, etc.

This is one way to do it. What this means then is if I hover over fetch Luke Skywalker, suddenly, it's inferred, because it's an async function, that we return a Promise Luke Skywalker. I can also do both if I want to. I can say Promise Luke Skywalker just here, which is pretty nice, Promise Luke Skywalker.

Now, if I return a number, let's say, then it's going to yell at me because data doesn't represent the thing. You notice here that we are lying to ourselves a little bit in all of these cases because, really, we don't know what this is going to return, or TypeScript itself doesn't know it. We're having to tell it.

The third way to do this is the most out there, or at least, you may have seen this syntax before if you've come across TypeScript, which is we're saying return data as Luke Skywalker. This syntax is a cast. What this does is it's casting data to Luke Skywalker. This could actually be anything.

I could say const matt -- This was always my dream when I was a kid -- as Luke Skywalker. Now, we can say that Matt is Luke Skywalker. If I were to change this to we need to ensure that Matt is assignable to Luke Skywalker, then we actually get errors here. Type thing is missing the following, blah, blah, blah, blah, blah, blah. It's yelling at me because I've not added all of these properties.

The as is a more powerful way, a stronger way to tell TypeScript what you think things should be. Of course, because TypeScript is really clever, then the return type of this is still going to be a Promise Luke Skywalker. We're still going to need to wait it and TypeScript is still relatively happy.

I do like that this shows you the difference between this as and this syntax, just the assigning syntax. This is a bit safer. What you should probably do in all cases is this. Sometimes you'll run up against cases where this is the better option.

Here, because data is any, any is a type that's assignable to anything. It's like TypeScript has no idea what it could be, and it literally doesn't. Unless it literally goes and fetches this data, it has no idea what that data shape is going to be. That's not TypeScript's business. It's not going to do that for you. We need to tell it. We can either do it with this syntax or with this syntax here.