The Assertion functions feature gives us a way to tell the TypeScript compiler that the subsequent flow of the code, after calling the Assertion function, some checks have been made. This was introduced in version 3.7. Here is the documentation about the feature.
Compiler doesn’t know our code
The issue with the compiler is that even though we have thrown an error in a function called prior, the compiler will still assume we have not check. Consider the following code:
function calculateStringLength(value: string | null) {
throwIfNull(value);
console.log(value.length);
// ~~~~~
// Error: Object is possibly 'null'.
}
function throwIfNull(value: string | null) {
if (typeof value !== "string") {
throw new Error("value is not string");
}
}
As we can see inside the calculateStringLength()
function, the compiler doesn’t know we have checked for string type in the throwIfNull()
function and therefore complains that value
is possibly null.
Returning with the asserts
keyword
In order to fix the above issue, we can provide more information for the throwIfNull()
function. We just need to type the return value of the function with the asserts
keyword along with the condition, then it will not throw an error.
function throwIfNull(value: string | null): asserts value is string {
if (typeof value !== "string") {
throw new Error("value is not string");
}
}
Now the compiler is happy and will know that value in calculateStringLength()
after calling throwIfNull()
will always be of a string
value and not null
.
Useful assertion-utility functions
Here are some useful utility functions that I use in my real world projects to narrow down the type. Will add more here if I find more on them.
export function assertTruthy<T>(
value: T,
message?: string,
): asserts value is NonNullable<T> {
if (!value) {
throw new Error(message || "Value is not truthy");
}
}
export function assertDefined<T>(
value: T,
message?: string,
): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error(message || "Value is not defined");
}
}
export function assertNumber(
value: number | null | undefined,
message?: string,
): asserts value is number {
if (typeof value !== "number") {
throw new Error(message || "Value is not a number");
}
}
Conclusion
Just use it! Jokes aside, these assertion functions not just help to narrow types, but also helpful to guard against values that are not intended to follow through to the next part of the code. On top of that, the code is more readable and cleaner, what not to like it?