Mastering TypeScript Functions: Handling Nullish Values Gracefully
Written on
Chapter 1: Introduction to TypeScript Helpers
In our NNS-dapp, we have integrated several invaluable TypeScript functions that significantly enhance our daily development experience. Among these, three specific functions for handling nullish values stand out: isNullish, nonNullish, and assertNonNullish.
Section 1.1: Understanding isNullish
How frequently have you found yourself writing if...else statements to check if a variable is undefined or null?
Here's a hypothetical example (assuming optional chaining is not available):
const test = (obj: MyObject | undefined | null) => {
if (obj === undefined || obj === null) {
return;}
obj.fn();
}
To streamline this process, we developed a helper function that utilizes generics and type predicates, which minimizes code duplication while ensuring type safety:
export const isNullish = <T>(argument: T | undefined | null): argument is undefined | null =>
argument === null || argument === undefined;
Using the generic type T allows the function to be applicable to any types declared in our project. The type guard effectively informs TypeScript that when the function checks a variable, it is confirming the variable is either undefined or null.
We can utilize this helper like so:
const test = (obj: MyObject | undefined | null) => {
if (isNullish(obj)) {
return;}
obj.fn();
}
Section 1.2: The nonNullish Function
At times, we may need to determine if a value is indeed defined. While it's possible to negate the isNullish function, we aim to maintain type safety.
This can be accomplished with the utility type NonNullable, which constructs a type that excludes undefined and null:
export const nonNullish = <T>(argument: T | undefined | null): argument is NonNullable<T> =>
!isNullish(argument);
With this, TypeScript recognizes that the object is defined:
const test = (obj: MyObject | undefined | null) => {
if (nonNullish(obj)) {
obj.fn();}
}
Chapter 2: Leveraging assertNonNullish
In addition to verifying conditions, it's often beneficial to throw errors when conditions are not met—especially when developing assertion patterns.
To enhance our previously created functions, we can implement assertion signatures, introduced in TypeScript 3.7, using the asserts keyword:
export class NullishError extends Error {}
export const assertNonNullish: <T>(
value: T,
message?: string
) => asserts value is NonNullable<T> = <T>(value: T, message?: string): void => {
if (isNullish(value)) {
throw new NullishError(message);}
};
By applying this to our earlier code, we can adjust the function to execute only if the guard passes:
const test = (obj: MyObject | undefined | null) => {
assertNonNullish(obj);
obj.fn();
}
The first video discusses optional properties and the non-null assertion in TypeScript, providing insights into effectively managing these concepts in your code.
The second video elaborates on the non-null assertion operator, focusing on how to properly use the exclamation mark in TypeScript expressions.
Conclusion
These helper functions have become so essential in my recent projects—including my latest "secret crazy" side endeavor—that I'm confident you'll find them just as valuable.
For more insights and updates, feel free to follow me on Twitter!