Narrow the unknown Type with TypeScript's Assertion Functions

Marius Schulz
InstructorMarius Schulz

Share this video with your friends

Send Tweet
Published a year ago
Updated a year ago

This lesson introduces assertion functions which let us narrow the type of a variable or property for the remainder of the containing scope. We're going to learn how to create an assertion function by using the asserts keyword in the function signature. In particular, we're going to focus on narrowing the unknown type to a more specific type.

Additional Reading

Instructor: [0:00] In the previous lesson, we worked with this range() function. Here, we're parsing the arguments zero and five to the from and to parameters. Let's go ahead and compile this project, and let's execute the resulting JavaScript file in Node. We can see that we get the range from zero to five excluding five itself.

[0:22] Now, let's see what happens when I try to parse strings as arguments. For example, I could try to create the range of letters from a through f. Now, TypeScript gives us a TypeError. This is because both from and to are expected to be numbers.

[0:37] I'm going to use an as any type assertion here to suppress these TypeErrors. I'm pretending that we're working with variables here which are typed as any. If I now run the program again, you can see that we get an error because we're parsing invalid arguments to the function. We've convinced ourselves that our parameter checks are working correctly.

[1:00] I want to go ahead and I want to refactor this check into an assert function. Let's say that we want to have a function called assert and our assert function is going to accept a condition, and it's going to accept a message. If the condition is not met, this function is supposed to throw an error and we're going to parse through our message. I'm going to use the assert function and verify it that the from and to params are actually numbers.

[1:31] I'm going to parse the same error message to the assert call. Now that we're using the assert function, we no longer need this if-statement. I'm going remove it. Unfortunately, our code is no longer type correct. Typescript is giving us a bunch of type errors.

[1:48] This is because from and to are now inferred to be of type unknown. Unlike before, from and to are no longer narrowed to type number. This is because typescript doesn't understand that if this condition is false our assert function will throw an error. We need to tell typescript explicitly that our assert function has that behavior. We do that by adding what's called an assertion signature.

[2:13] This assertion signature says that whatever gets parsed to the condition parameter must be true if our assert function returns regularly and doesn't throw an error. Notice that this is the behavior we've implemented here. If the condition isn't true, we throw an error. Otherwise, we implicitly return. Typescript understands the implications that this behavior has on the control flow of the program.

[2:39] If control flow makes it to line 16, it means this condition must have been true, because otherwise assert would have thrown an error. This means that at this point, TypeScript can safely assume from and to to be of type number.

[2:55] Instead of this combined assert call, we could have also written two separate assert calls. We can first assert that the from parameters of type number and then we can assert that the two parameters of type number. The effect of these assert calls is the same as before.

[3:11] If control flow makes it parse these assert calls, from and to can safely be assumed to be of type number. If I go ahead and run our code again, we can see that we get the correct error message saying that from must be a number.

[3:27] Next up, I want to show you how we can write a more specialized assert function. Let's go ahead and delete the one we currently have. Let's say, I want to write an assert function called, Assert is Number. This function is going to accept a value of type unknown and it's going to accept a string.

[3:47] Within the body of the function, we're going to say that if the type of value is not a number, we're going to throw an error. We also need to add an assertion signature to our function. This time, we're going to assert that value is a number. This assertion signature says that unless our functions throws an error, value can be narrowed to type number.

[4:11] With this assertion signature in place, we can go ahead, remove our two assert calls, and use our new function. With these checks in place, you can see that we no longer get any type errors. Let's run one final check to make sure we still get the right error and indeed, we do.

Skylar Bolton
Skylar Bolton
~ a year ago

Well done! I can see multiple uses for this kind of reasoning and "defense" when writing open-source packages.