What is the difference between "asserts value is type" and "value is type" in TypeScript?
Asked Answered
M

1

9

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?

Moritz answered 31/1, 2022 at 18:46 Comment(0)
P
12

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:

TS Playground

// 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`

Presentable answered 31/1, 2022 at 18:54 Comment(2)
Amazing how one can learn something new everyday by wandering on SO =)Jimenez
@MatthieuRiegler You should wander around the issues in the TS repo! (That link is a view sorted by most 👍 reactions).Presentable

© 2022 - 2024 — McMap. All rights reserved.