Assertion Functions

Assertion functions — специальные функции TypeScript, которые при выполнении сужают тип аргумента в вызывающем коде: если функция не бросила исключение, компилятор считает условие доказанным.

Зачем нужно

Позволяют инкапсулировать проверку инварианта в одной функции и при этом получить narrowing в вызывающем коде — без повторных type guard-условий. Это особенно удобно в коде с assert-паттерном, когда программа должна упасть при нарушении предусловия.

Где используется

  • Валидация входных параметров в контроллерах и сервисах
  • Assertion-хелперы в тестах (assertDefined, assertType)
  • Работа с unknown данными после парсинга JSON/конфига
  • Замена повторяющихся if (!x) throw в бизнес-логике

Основной контент

Assertion function — функция с сигнатурой asserts value или asserts value is Type. Если она возвращает нормально (не бросает), компилятор обновляет тип переменной в вызывающем scope.

asserts condition

function assert(condition: unknown, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

function processUser(user: User | null) {
  assert(user !== null, "user must not be null");
  // здесь user: User — narrowing произошёл
  console.log(user.name);
}

asserts value is Type

function assertIsString(val: unknown): asserts val is string {
  if (typeof val !== "string") {
    throw new TypeError(`Expected string, got ${typeof val}`);
  }
}

function greet(input: unknown) {
  assertIsString(input);
  // здесь input: string
  console.log(input.toUpperCase());
}

Assertion functions vs type guards

// Type guard — возвращает boolean, narrowing только внутри if
function isString(val: unknown): val is string {
  return typeof val === "string";
}

// Assertion function — бросает, narrowing после вызова
function assertString(val: unknown): asserts val is string {
  if (typeof val !== "string") throw new TypeError("Not a string");
}

// Использование type guard
if (isString(x)) {
  // x: string здесь
}

// Использование assertion function
assertString(x);
// x: string отсюда и до конца блока

Реальный пример: парсинг конфига

interface Config {
  port: number;
  host: string;
}

function assertIsConfig(val: unknown): asserts val is Config {
  if (
    typeof val !== "object" ||
    val === null ||
    typeof (val as Config).port !== "number" ||
    typeof (val as Config).host !== "string"
  ) {
    throw new Error("Invalid config shape");
  }
}

const raw: unknown = JSON.parse(process.env.APP_CONFIG ?? "{}");
assertIsConfig(raw);
// raw: Config — типобезопасно дальше
console.log(`Starting on ${raw.host}:${raw.port}`);

Частые ошибки

  • Не указать asserts в сигнатуре — функция будет обычной, narrowing не произойдёт.
  • Вернуть значение вместо броска — assertion function должна возвращать void; любой return value вызовет ошибку компилятора.
  • Путать с type guard — type guard возвращает boolean и сужает тип внутри if, assertion function сужает после вызова.
  • Использовать в async функциях — narrowing после await assertSomething(x) пока не поддерживается стабильно; лучше проверять синхронно.

Связанные темы

Ресурсы