๐ŸŽ“ A1Learner
๐Ÿ”
0 of 42 lessons completed

TypeScript Typed Errors

A typed error is an Error subclass that carries extra fields with set types. Code that catches it can narrow with instanceof and read those fields with full checking.

A custom error with a code

ApiError carries a readonly code beside the message. The catch narrows with instanceof before reading any field.

class ApiError extends Error {
  readonly code: number;

  constructor(message: string, code: number) {
    super(message);
    this.name = 'ApiError';
    this.code = code;
  }
}

try {
  throw new ApiError('Not found', 404);
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.name);
    console.log(err.code);
    console.log(err.message);
  }
}

Telling sibling errors apart

Two sibling classes share one catch block. A chain of instanceof checks routes each throw to its own line.

class NotFoundError extends Error {
  readonly status = 404;

  constructor(message: string) {
    super(message);
    this.name = 'NotFoundError';
  }
}

class AuthError extends Error {
  readonly status = 401;

  constructor(message: string) {
    super(message);
    this.name = 'AuthError';
  }
}

function loadRecord(id: number): string {
  if (id === 0) {
    throw new AuthError('Login required');
  }
  throw new NotFoundError('No record ' + id);
}

function showRecord(id: number): void {
  try {
    console.log(loadRecord(id));
  } catch (err) {
    if (err instanceof NotFoundError) {
      console.log('Missing: ' + err.message);
    } else if (err instanceof AuthError) {
      console.log('Blocked: ' + err.message);
    } else {
      console.log('Unknown failure');
    }
  }
}

showRecord(7);
showRecord(0);

Example

class ValidationError extends Error {
  readonly field: string;

  constructor(field: string, message: string) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

function parseAge(text: string): number {
  const value = Number(text);
  if (Number.isNaN(value)) {
    throw new ValidationError('age', 'Age must be a number');
  }
  return value;
}

function reportAge(text: string): void {
  try {
    console.log(parseAge(text));
  } catch (err) {
    if (err instanceof ValidationError) {
      console.log(err.field + ': ' + err.message);
    } else if (err instanceof Error) {
      console.log('Error: ' + err.message);
    } else {
      console.log('Unknown failure');
    }
  }
}

reportAge('36');
reportAge('abc');

Key points

  • A custom error class extends Error and adds readonly fields for the facts a handler needs.
  • Setting the name field in the constructor gives every instance a stable label in logs.
  • Under strict mode the catch variable has type unknown, so it must be narrowed before use.
  • The instanceof operator narrows an unknown catch value to the error class it matches.
  • The target es2020 compiles classes natively, so instanceof works on Error subclasses without prototype fixes.

In this example, a validation error carries the field name beside its message, and the catch block prints both for the bad input after printing the parsed age for the good one.

Note

Sibling error classes let one catch block route each failure to its own response.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

Why must a catch variable be narrowed before its fields are read?