TypeScript has is
operator which helps to create a test function for type checking. Recently I saw two different implementations of this operator, one of which uses asserts
keyword.
I didn't find information about the difference of the two ways of use in the docs. I played with it a little and if I'm correct, asserts
doesn't let you return anything from the function, but other than this I didn't find any differences.
Here is the code I tested with:
// Asserts and tests the value without returninng anything
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") throw Error("value is not a string");
}
// Tests the value and returns something so it can be used for typecheck
// more explicitly
function testIsString(value: unknown): value is string {
return typeof value === "string";
}
const string = "hello";
const number = 123;
assertIsString(string); // does nothing
assertIsString(number); // throws Error
testIsString(string); // returns true
testIsString(number); // returns false
Question: Are there other differences between the two use cases?
Summary: The main difference is that one throws while the other has to be used in a conditional.
The functions which potentially throw an exception and return void
are called assertion functions.
These make an assertion (you might think of it as creating a contract with the compiler), that if the function doesn't throw an exception, the predicate in the return value will be true. From that point onward (within the current scope), the type information in the predicate will be in effect.
The functions which return boolean
values are called type predicates.
Instead of potentially throwing an exception (and causing your program to crash unless it's caught — see try...catch
), these simply return a boolean value. If the boolean is true, then for the remainder of the scope where the predicate was invoked (e.g. a block of code), the predicate will be in effect.
The documentation links have several examples for each case (and additional information). Here's a demo:
// predicate
function exists<T>(maybe: T): maybe is NonNullable<T> {
return maybe != null;
}
// assertion
function assertExists<T>(maybe: T): asserts maybe is NonNullable<T> {
if (maybe == null) throw new Error(`${maybe} doesn't exist`);
}
function example1() {
console.log("example1 begin");
let maybe: string | undefined;
if (exists(maybe)) {
maybe; // string
} else {
maybe; // undefined
}
console.log("example1 end");
}
function example2() {
console.log("example2 begin");
let maybe: string | undefined;
assertExists(maybe);
maybe; // string
console.log("example2 end");
}
example1(); // 'example1 begin' then 'example1 end'
example2(); // only 'example2 begin', then exception is thrown: `undefined doesn't exist`