Invariant-style assertion functions in TypeScript

There is this invariant() in React source code that I found pretty useful.

It is used to assert some condition should be truthy, if not then an error should be thrown, we can simplify it to something like below.

ts
function invariant(condition: any, message: string) {
if (!condition) {
throw new Error(message);
}
}
ts
function invariant(condition: any, message: string) {
if (!condition) {
throw new Error(message);
}
}

cases where it is useful

In a backend API which receives user input from client, we need to do data validation.

ts
function handleInput(input: string | null) {
if (input == null) {
throw new Error("input should not be null");
}
// input is string now
const validaInput = input;
}
ts
function handleInput(input: string | null) {
if (input == null) {
throw new Error("input should not be null");
}
// input is string now
const validaInput = input;
}

If we are to do it in variant style, it’ll be like this.

ts
function handleInput(input: string | null) {
invariant(input, "input should not be null");
const validaInput = input;
(parameter) input: string | null
}
ts
function handleInput(input: string | null) {
invariant(input, "input should not be null");
const validaInput = input;
(parameter) input: string | null
}

But TypeScript doesn’t infer that input is not null here,validaInput is still string | null.

Assertion Function comes as rescue.

Actually it was added a long time ago in TypeScript 3.7, we can use assertion function here to tell TypeScript that if invariant doesn’t throw an error, then the input is not null.

We need invariant() to be a generic function to suit more cases.

ts
function invariant<T>(
data: T,
message: string
): asserts data is NonNullable<T> {
if (!data) {
throw new Error(message);
}
}
ts
function invariant<T>(
data: T,
message: string
): asserts data is NonNullable<T> {
if (!data) {
throw new Error(message);
}
}

Now if we try the code, validaInput has the right type of string.

Or we can assert data to be some specific type.

ts
function assertsString(data: any): asserts data is string {
if (typeof data !== "string") {
throw new Error("data should be string");
}
}
ts
function assertsString(data: any): asserts data is string {
if (typeof data !== "string") {
throw new Error("data should be string");
}
}

Which is basically the same as type guard, but without the usage of if clause.

ts
function isString(data: any): data is string {
return typeof data === "string";
}
 
function fun(foo: any) {
if (isString(foo)) {
type A = typeof foo
type A = string
}
}
ts
function isString(data: any): data is string {
return typeof data === "string";
}
 
function fun(foo: any) {
if (isString(foo)) {
type A = typeof foo
type A = string
}
}

I personally like invariant-style better, how do you think?

😳 Would you like to share my post to more people ?    

❮ Prev: How does useImperativeHandle() work internally?

Next: How does React bailout work in reconciliation?