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
functioninvariant (condition : any,message : string) {if (!condition ) {throw newError (message );}}
ts
functioninvariant (condition : any,message : string) {if (!condition ) {throw newError (message );}}
cases where it is useful
In a backend API which receives user input from client, we need to do data validation.
ts
functionhandleInput (input : string | null) {if (input == null) {throw newError ("input should not be null");}// input is string nowconstvalidaInput =input ;}
ts
functionhandleInput (input : string | null) {if (input == null) {throw newError ("input should not be null");}// input is string nowconstvalidaInput =input ;}
If we are to do it in variant style, itâll be like this.
ts
functionhandleInput (input : string | null) {invariant (input , "input should not be null");constvalidaInput =input ;}
ts
functionhandleInput (input : string | null) {invariant (input , "input should not be null");constvalidaInput =input ;}
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
functioninvariant <T >(data :T ,message : string): assertsdata isNonNullable <T > {if (!data ) {throw newError (message );}}
ts
functioninvariant <T >(data :T ,message : string): assertsdata isNonNullable <T > {if (!data ) {throw newError (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
functionassertsString (data : any): assertsdata is string {if (typeofdata !== "string") {throw newError ("data should be string");}}
ts
functionassertsString (data : any): assertsdata is string {if (typeofdata !== "string") {throw newError ("data should be string");}}
Which is basically the same as type guard, but without the usage of if clause
.
ts
functionisString (data : any):data is string {return typeofdata === "string";}Âfunctionfun (foo : any) {if (isString (foo )) {typeA = typeoffoo }}
ts
functionisString (data : any):data is string {return typeofdata === "string";}Âfunctionfun (foo : any) {if (isString (foo )) {typeA = typeoffoo }}
I personally like invariant-style better, how do you think?