TypeScript: isNullish, nonNullish and assertNonNullish

Three little TypeScript assertion functions that have proven to be really useful.

Dec 30, 2022

#typescript #programming #webdevelopment

Nil in the Alps

Baptiste Buisson

There are few gems we have implemented in NNS-dapp that makes our devs life easier on daily basis. Among those, the following three little TypeScript functions have proven to be really useful.


isNullish

How often have you code if...else statement that checks if an object is undefined or null?

// Pseudo code (assuming optional chaining does not exist 😉) const test = (obj: MyObject | undefined | null) => { if (obj === undefined || obj === null) { return; } obj.fn(); }

Thanks to generics and type predicates, we have developed a helper that makes us avoid code duplication while preserving type safety.

export const isNullish = <T>(argument: T | undefined | null): argument is undefined | null => argument === null || argument === undefined;

The use of a generic T will scope the usage of the function to the types we have declared in our project.

As for the type guard, it narrows TypeScript to understand that the variable is indeed from the specific expected type. In other words, this function makes TypeScript understands that the parameter is - if it matches the checks of the function - indeed undefined or null.

We can use the helper as following:

const test = (obj: MyObject | undefined | null) => { // 1. Avoid code duplication if (isNullish(obj)) { return; } // 2. TypeScript checks it is defined obj.fn(); }

nonNullish

Sometimes we need the opposite, we need to know if something is defined. While we can negate previous shorthand function to get to know if it is the case, we also want to preserve the type safety.

This can be achieved by using the utility NonNullable to construct a type by excluding undefined and null.

export const nonNullish = <T>(argument: T | undefined | null): argument is NonNullable<T> => !isNullish(argument);

That way, TypeScript will understand that indeed, an object is defined.

const test = (obj: MyObject | undefined | null) => { //1. Avoid code duplication if (nonNullish(obj)) { // 2. TypeScript checks it is defined obj.fn(); } }

assertNonNullish

In addition to checking if specified conditions are truthy, throwing errors if these are falsy can be handy. Notably for the development of guards with assertion patterns.

For such purpose, we can enhance the functions we have previously developed with the assertion signatures concept that has been introduced in TypeScript 3.7.

Using the asserts keyword and a condition we can make TypeScript aware that a helper will perform a check and throw an error if conditions are not met.

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); } };

Applied to our previous code snippet, we can transform the function to execute the code only if the guard is matching.

const test = (obj: MyObject | undefined | null) => { // 1. Avoid code duplication // 2. TypeScript understands it might throw an error assertNonNullish(obj); // 3. TypeScript checks it is defined obj.fn(); }

Conclusion

I find these helpers so useful that I now use these in any of my recent works - including my new "secret crazy" side project - and I bet you will do too 😁.