Type-safe Data Fetching with unknown in TypeScript
This post will cover the unknown
type and how it can be used for type-safe data fetching.
An any
type refresher
The any
type could be used when we are unsure of the type of value. The problem with any
is that no type checks will be carried out on values of type any
.
What if there was a type like any
that can be used for values we don’t know but was also type-safe? This is what the unknown
type is!
Understanding the unknown
type
Consider the function below:
function add(a: unknown, b: unknown) {
return a + b; // 💥 Object is of type 'unknown'
}
The code contains a function that adds the parameter values together and returns the result. The parameters are of type unknown
.
Type errors occur where a
and b
are referenced because variables of type unknown
can’t be added together.
So, type checking does occur on the unknown
type, unlike the any
type.
If we update the function to have the implementation below, the type errors disappear:
function add(a: unknown, b: unknown) {
if ( typeof a === "number" && typeof b === "number" ) { return a + b;
} return 0;}
The if
statement on line 2 is called a type guard. The type guard allows TypeScript to adjust the types of a
and b
from unknown
to number
. Code within the if
statement can then operate on a
and b
.
You can’t operate directly on variables of type
unknown
. We have to give TypeScript information to narrow the type so that it can be used.
Type-safe data fetching
Now that we understand the unknown
type, let’s use it to fetch data in a strongly-typed manner. Consider the code below:
async function getData(
path: string
): Promise<unknown> {
const response = await fetch(path);
return await response.json();
}
This is a utility function that calls a web API and returns the data in the response body. The data that is returned from the function is given the unknown
type.
Let’s consume this utility function to get a person from a web API:
type Person = {
id: string;
name: string;
};
async function getPerson(
id: string
): Promise<Person | null> {
const person = await getData("/people/1");
return person; // 💥 Type 'unknown' is not assignable to type 'Person | null'.}
A type error occurs on the highlighted line, where the person
variable is returned from the getPerson
function. This is because person
is of type unknown
, which isn’t compatible with Person
or null
.
We need to check that person
is in fact of type Person
to resolve the type error. We can use what’s called a type predicate to do this:
function isPerson(
person: any
): person is Person {
return "id" in person && "name" in person;
}
Notice the return type, person is Person
. This is a type predicate; it is a special return type that the Typescript compiler uses to know what type a particular value is.
Let’s use the isPerson
type to narrow person
from unknown
to Person
:
async function getPerson(
id: string
): Promise<Person | null> {
const person = await getData("/people/1");
if (person && isPerson(person)) { return person; // type is narrowed to `Person`
}
return null;
}
The type error disappears as we expect.
Neat!
Summary
The unknown
type allows us to reduce our use of any
and create more strongly-typed code. We write a little more code when using unknown
, but the confidence we get from knowing our code is type-safe is well worth it.
Did you find this post useful?
Let me know by sharing it on Twitter.If you to learn more about TypeScript, you may find my free TypeScript course useful: