Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Error Handling with Types in TypeScript

Error Handling with Types

~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Our checkOut() method (chapter 14) has so far only returned boolean – success or failure, but NO information about WHY something failed. This chapter shows two better patterns: custom error classes and the "result type" pattern.

A fundamental TypeScript issue: catch(error)

try {
  library.checkOut('123');
} catch (error) {
  console.log(error.message); // error! error has type 'unknown', NOT 'Error'
}

In modern TypeScript (since version 4.4, with strict enabled), error in a catch block has the type unknown by default (chapter 4) – NOT Error, because JavaScript technically allows throwing ANYTHING (throw 'a string', throw 42 are both valid). TypeScript FORCES you to account for that.

try {
  library.checkOut('123');
} catch (error) {
  if (error instanceof Error) { // type narrowing from chapter 19!
    console.log(error.message); // ✓ now allowed - error is recognized as Error
  } else {
    console.log('Unknown error:', error);
  }
}

Custom error classes

src/errors/LibraryError.ts
export class LibraryError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'LibraryError';
  }
}

export class MediumNotFoundError extends LibraryError {
  constructor(public readonly isbn: string) {
    super(`Medium with ISBN ${isbn} was not found.`);
    this.name = 'MediumNotFoundError';
  }
}

export class MediumNotAvailableError extends LibraryError {
  constructor(public readonly isbn: string, public readonly currentStatus: string) {
    super(`Medium ${isbn} is not available (status: ${currentStatus}).`);
    this.name = 'MediumNotAvailableError';
  }
}

EXACTLY inheritance from chapter 13, applied to the built-in Error class – MediumNotFoundError and MediumNotAvailableError carry ADDITIONAL, typed information (isbn, currentStatus) beyond the plain message string.

// In Library.ts:
checkOut(isbn: string): void {
  const medium = this.stock.find((m) => m.isbn === isbn);
  if (!medium) {
    throw new MediumNotFoundError(isbn);
  }
  if (medium.status !== 'available') {
    throw new MediumNotAvailableError(isbn, medium.status);
  }
  medium.status = 'borrowed';
}
try {
  library.checkOut('999');
} catch (error) {
  if (error instanceof MediumNotFoundError) {
    console.log(`Please check the ISBN: ${error.isbn}`); // typed access to isbn!
  } else if (error instanceof MediumNotAvailableError) {
    console.log(`Status was: ${error.currentStatus}`);
  }
}

Alternative: the result type pattern

throw/catch has a downside: the function's TYPE itself does NOT show that an error is possible – callers have to read the documentation or "just know". The result type pattern makes errors EXPLICITLY part of the signature:

src/types/Result.ts
export type Result<T, E = Error> =
  | { success: true; value: T }
  | { success: false; error: E };

export function success<T>(value: T): Result<T, never> {
  return { success: true, value };
}

export function failure<E>(error: E): Result<never, E> {
  return { success: false, error };
}
checkOut(isbn: string): Result<void, LibraryError> {
  const medium = this.stock.find((m) => m.isbn === isbn);
  if (!medium) {
    return failure(new MediumNotFoundError(isbn));
  }
  if (medium.status !== 'available') {
    return failure(new MediumNotAvailableError(isbn, medium.status));
  }
  medium.status = 'borrowed';
  return success(undefined);
}

const result = library.checkOut('123');
if (result.success) {
  console.log('Checkout successful!'); // TypeScript knows: result.value exists (discriminated union, chapter 9!)
} else {
  console.log(`Error: ${result.error.message}`); // TypeScript knows: result.error exists
}

Result<T, E> is a discriminated union (chapter 9) COMBINED with generics (chapter 17) – the success boolean is the discriminator. The BIG advantage: the RETURN TYPE Result<void, LibraryError> shows IMMEDIATELY, without reading documentation, that this method CAN fail – a caller reading result.value WITHOUT first checking result.success gets a COMPILE-TIME error, not just a runtime crash.

PatternWhen it fits
throw/catchFamiliar, JavaScript standard, works well for UNEXPECTED, rare errors (programming bugs, corrupted data).
Result typeErrors are part of the TYPE signature, handling is ENFORCED by the compiler, ideal for EXPECTED, frequent "failure cases" (validation, business logic rules like "book not available").

Tipp: Rule of thumb: throw for GENUINELY EXCEPTIONAL situations (a bug, corrupted data, a network that's completely unreachable), the result type for EXPECTABLE "error paths" that ARE part of the normal program flow ("book already checked out" is not a BUG, but a normal business case). Both patterns can coexist in the same project.