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
- JavaScript's built-in error system—Error, TypeError, RangeError, and other subclasses
- Use
instanceofin thecatchblock to narrow down the error type and safely access properties of a specific error - Create a custom error class that inherits from
Errorand adds business-specific properties (such asfieldandstatusCode). - The
Resultpattern makes errors part of the return value—a distinguishable union ofokanderror - The error type in the
catchblock isunknown—useinstanceofor type guards to handle it safely - Always throw an
Errorobject; do not throw strings or numbers
📝 Exercises
- Basic Problem (Difficulty ⭐): Write a function
safeParse(json: string): Result<object, Error>that wrapsJSON.parsein atry/catchblock and returnssuccesson success andfailureon failure. - Advanced Problem (Difficulty ⭐⭐): Define the base class
AppErrorand the subclassesDatabaseError(with aqueryproperty) andAuthError(with astatusCodeproperty). Write a function that returns different HTTP status codes and messages based on the error type. - 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 afetchWithRetryfunction that automatically retries in case of failure.