TypeScript: TypeScript Error Handling

Last updated: 2026-08-26

Error handling is an essential part of any project—TypeScript’s type system makes error handling safer and more predictable.

1. The Basics of Error Handling in JavaScript

(1) try/catch/finally

TYPESCRIPT
try {
  let data = JSON.parse(input);
  console.log(data);
} catch (error) {
  // error The type is unknown(strict In mode)
  console.log("Parsing Failed");
} finally {
  console.log("Free Up Resources");
  // This will be executed regardless of success or failure
}

(2) throw statement

JavaScript can throw any value—not just Error objects:

TYPESCRIPT
throw new Error("An error occurred");        // ✅ Standard Practice
throw "An error occurred";                    // ⚠️ It's possible, but not ideal.
throw 404;                         // ⚠️ It's possible, but not ideal.
throw { code: 500, msg: "Server Error" }; // ⚠️ It's possible, but not ideal.
📌 Recommendation: Always throw an Error object (or one of its subclasses)—it carries call stack information, which makes debugging easier. Throwing a string or a number does not provide call stack information.

(3) Error Types

TYPESCRIPT
interface Error {
  name: string;     // Error name (e.g., "TypeError", "RangeError")
  message: string;  // Error Description
  stack?: string;   // Call Stack(Non-standard but widely supported)
}


2. Built-in Error Types and Type Narrowing

(1) Common Built-in Errors

Error Type Triggered By
Error General Error
TypeError Type error (e.g., null.toString())
RangeError Value out of range (e.g., recursive overflow)
SyntaxError Syntax error (e.g., JSON.parse failed)
ReferenceError Reference to an undefined variable
URIError URI encoding/decoding error

(2) Narrowing the types of errors with instanceof

TYPESCRIPT
function processValue(value: unknown) {
  try {
    let num = Number(value);
    if (isNaN(num)) throw new TypeError("Not a valid number");
    if (num < 0) throw new RangeError("Cannot be a negative number");
    return num;
  } catch (error) {
    if (error instanceof TypeError) {
      console.log(`Type error:${error.message}`);  // ✅ narrowed to TypeError
    } else if (error instanceof RangeError) {
      console.log(`Range error:${error.message}`);  // ✅ narrowed to RangeError
    } else if (error instanceof Error) {
      console.log(`Other Errors:${error.message}`);  // ✅ narrowed to Error
    } else {
      // In theory, it won't reach there.——JavaScript Always throw Error or its subclasses
      console.log("Unknown error");
    }
  }
}


3. Custom Error Classes

(1) Basic Custom Errors

TYPESCRIPT
class AppError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "AppError";  // Incorrect setting name
  }
}

class ValidationError extends AppError {
  constructor(
    message: string,
    public field: string    // Additional error messages
  ) {
    super(message);
    this.name = "ValidationError";
  }
}

class NotFoundError extends AppError {
  constructor(
    public resource: string,
    public id: number | string
  ) {
    super(`${resource} (${id}) Does not exist`);
    this.name = "NotFoundError";
  }
}

(2) Using Custom Errors

TYPESCRIPT
function findUser(id: number) {
  if (id <= 0) {
    throw new ValidationError("IDMust be a positive number", "id");
  }
  // Simulated Search
  if (id > 100) {
    throw new NotFoundError("User", id);
  }
  return { id, name: "User" + id };
}

try {
  let user = findUser(999);
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Verification Failed:Field ${error.field},${error.message}`);
  } else if (error instanceof NotFoundError) {
    console.log(`Not found:${error.resource},ID=${error.id}`);
  } else if (error instanceof Error) {
    console.log(`Error:${error.message}`);
  }
}

▶ Example: HTTP Error System

Output:

TEXT 📖 Display only
Division by zero
TYPESCRIPT
class HttpError extends Error {
  constructor(
    public statusCode: number,
    message: string
  ) {
    super(message);
    this.name = "HttpError";
  }
}

class BadRequestError extends HttpError {
  constructor(message: string) {
    super(400, message);
    this.name = "BadRequestError";
  }
}

class UnauthorizedError extends HttpError {
  constructor(message: string = "Unauthorized") {
    super(401, message);
    this.name = "UnauthorizedError";
  }
}

class ForbiddenError extends HttpError {
  constructor(message: string = "Access Denied") {
    super(403, message);
    this.name = "ForbiddenError";
  }
}

class NotFoundError2 extends HttpError {
  constructor(resource: string) {
    super(404, `${resource} Does not exist`);
    this.name = "NotFoundError";
  }
}

// Usage
function handleRequest(path: string) {
  if (!path.startsWith("/api/")) {
    throw new BadRequestError("The path must begin with /api/ Introduction");
  }
  if (path === "/api/admin") {
    throw new ForbiddenError();
  }
  return { data: "Response Data" };
}

try {
  let result = handleRequest("/api/users");
  console.log(result.data);
} catch (error) {
  if (error instanceof HttpError) {
    console.log(`HTTP ${error.statusCode}: ${error.message}`);
  }
}

Output:

TEXT 📖 Display only
Response Data


4. Result Pattern

The problem with try/catch is that "exceptions are implicit"—the function signature doesn't tell you that it might throw an exception. The Result pattern makes errors part of the return value:

(1) Define the Result type

TYPESCRIPT
type Success<T> = { ok: true; value: T };
type Failure<E> = { ok: false; error: E };
type Result<T, E = Error> = Success<T> | Failure<E>;

(2) Create Result

TYPESCRIPT
function success<T>(value: T): Success<T> {
  return { ok: true, value };
}

function failure<E>(error: E): Failure<E> {
  return { ok: false, error };
}

(3) Using Result Instead of try/catch

TYPESCRIPT
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return failure("The divisor cannot be zero.");
  }
  return success(a / b);
}

let result1 = divide(10, 2);
let result2 = divide(10, 0);

if (result1.ok) {
  console.log(result1.value);  // 5 ✅
} else {
  console.log(result1.error);
}

if (result2.ok) {
  console.log(result2.value);
} else {
  console.log(result2.error);  // "The divisor cannot be zero." ✅
}

(4) Result: Tool-Based Methods

TYPESCRIPT
function tryCatch<T>(fn: () => T): Result<T, Error> {
  try {
    return success(fn());
  } catch (error) {
    return failure(error instanceof Error ? error : new Error(String(error)));
  }
}

async function tryAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>> {
  try {
    return success(await fn());
  } catch (error) {
    return failure(error instanceof Error ? error : new Error(String(error)));
  }
}

// Usage
let parseResult = tryCatch(() => JSON.parse('{"name":"Charlie"}'));
if (parseResult.ok) {
  console.log(parseResult.value.name);  // "Charlie"
}


5. Handling error types in the catch block

(1) Under strict mode, error is unknown

TYPESCRIPT
try {
  JSON.parse("invalid");
} catch (error) {
  // error The type is unknown(TypeScript 4.4+)
  // console.log(error.message);  // ❌ unknown NonemessageProperties

  // ✅ Method 1:instanceof Inspection
  if (error instanceof Error) {
    console.log(error.message);
  }

  // ✅ Method 2:Type Assertion(Caution)
  let msg = (error as Error).message;

  // ✅ Method 3:Type Guard Function
  function getErrorMessage(error: unknown): string {
    if (error instanceof Error) return error.message;
    if (typeof error === "string") return error;
    return "Unknown error";
  }
  console.log(getErrorMessage(error));
}

(2) The error type in older versions of TypeScript

TYPESCRIPT
// TypeScript 4.3 and earlier——catch 's error The type is any
// You can tsconfig Useful useUnknownInCatchVariables: true Change to unknown

// TypeScript 4.4+——Default unknown(strict In mode)

▶ Example: Narrowing Unknown Error Types

Output:

TEXT 📖 Display only
Division by zero
TYPESCRIPT
function safeReadFile(path: string): string | null {
  try {
    let content = "[simulated file content]";
    if (Math.random() > 0.5) throw new SyntaxError("Parse error");
    return content;
  } catch (error: unknown) {
    if (error instanceof SyntaxError) {
      console.log(`Syntax issue: ${error.message}`);
    } else if (error instanceof Error) {
      console.log(`Error: ${error.message}`);
    } else {
      console.log("Unknown error occurred");
    }
    return null;
  }
}

let data = safeReadFile("config.json");

Output:

TEXT 📖 Display only
Division by zero

▶ Example: Type Guard for Error Handling

Output:

TEXT 📖 Display only
Division by zero
TYPESCRIPT
function isError(value: unknown): value is Error {
  return value instanceof Error;
}

function getErrorMessage(error: unknown): string {
  if (isError(error)) return error.message;
  if (typeof error === "string") return error;
  if (typeof error === "number") return `Error code: ${error}`;
  return "An unknown error occurred";
}

type AppResult<T> = { ok: true; value: T } | { ok: false; error: string };

function divide(a: number, b: number): AppResult<number> {
  if (b === 0) return { ok: false, error: "Division by zero" };
  return { ok: true, value: a / b };
}

let r = divide(10, 0);
if (!r.ok) console.log(getErrorMessage(r.error)); // Division by zero

Output:

TEXT 📖 Display only
Division by zero

❓ FAQ

Q Which should I use, try/catch or the Result pattern?
A The two approaches are not mutually exclusive. try/catch is the standard exception handling mechanism in JavaScript/TypeScript—suitable for unpredictable runtime errors (such as network failures or JSON parsing issues). The Result pattern is suitable for predictable business errors—the function signature explicitly states that "failure is possible," and the caller must handle it. Just maintain a consistent style throughout your project.
Q Why is the error caught by catch unknown instead of Error?
A Because JavaScript allows any value to be thrown—both throw "message" and throw 42 are valid. TypeScript cannot guarantee that the value caught by catch will be an Error instance, so using unknown is the safest approach. In actual development, 99% of throws are Error objects—you can narrow it down using instanceof Error.
Q Does a custom error class need to call super?
A Yes, it must. When a custom error class inherits from Error, it must call super(message) in the constructor to initialize the message property of Error. It is also recommended to set this.name manually—otherwise, name defaults to the name of the parent class.
Q Does the code in the finally block affect the return value?
A A return statement in the finally block overrides the return statement in the try/catch block—this is a common source of bugs. It is recommended to use the finally block only for resource cleanup (closing files, releasing locks, etc.) and not to include return statements there.

📖 Summary

📝 Exercises

  1. Basic Problem (Difficulty ⭐): Write a function safeParse(json: string): Result<object, Error> that wraps JSON.parse in a try/catch block and returns success on success and failure on failure.
  2. Advanced Problem (Difficulty ⭐⭐): Define the base class AppError and the subclasses DatabaseError (with a query property) and AuthError (with a statusCode property). Write a function that returns different HTTP status codes and messages based on the error type.
  3. Challenge (Difficulty: ⭐⭐⭐): Implement tryAsync<T>(fn: () => Promise<T>): Promise<Result<T, AppError>> to uniformly convert exceptions from asynchronous operations into the AppError system (distinguishing between network errors, timeout errors, and server errors). Write a fetchWithRetry function that automatically retries in case of failure.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏