TypeScriptのエラー処理
エラー処理は、あらゆるプロジェクトにおいて不可欠な要素です。TypeScriptの型システムにより、エラー処理はより安全で予測しやすくなります。
1. 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文
JavaScript では、Error オブジェクトだけでなく、あらゆる値をスローすることができます:
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.
📌 推奨事項: 常に
Error オブジェクト(またはそのサブクラスのいずれか)をスローするようにしてください。これにはコールスタック情報が含まれており、デバッグが容易になります。文字列や数値をスローしても、コールスタック情報は得られません。
(3) エラーの種類
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. 組み込みのエラー型と型の絞り込み
(1) よくある組み込みのエラー
| エラーの種類 | 発生原因 |
|---|---|
Error |
一般的なエラー |
TypeError |
タイプエラー(例:null.toString()) |
RangeError |
値が範囲外(例:再帰的オーバーフロー) |
SyntaxError |
構文エラー(例:JSON.parse が失敗しました) |
ReferenceError |
未定義の変数への参照 |
URIError |
URIのエンコード/デコードエラー |
(2) 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. カスタムエラークラス
(1) 基本的なカスタムエラー
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) カスタムエラーの使用
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}`);
}
}
▶ 例:HTTPエラーシステム
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}`);
}
}
出力:
TEXT
Response Data
4. 結果のパターン
try/catch の問題点は、「例外が暗黙的」であることです。つまり、関数のシグネチャからは、例外がスローされる可能性があることがわかりません。Result パターンでは、エラーを戻り値の一部として扱います:
(1) 結果の型を定義する
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) 結果の作成
TYPESCRIPT
function success<T>(value: T): Success<T> {
return { ok: true, value };
}
function failure<E>(error: E): Failure<E> {
return { ok: false, error };
}
(3) try/catch の代わりに Result を使用する
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) 結果:ツールベースの手法
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. catch ブロックにおけるエラータイプの処理
(1) 厳密モードでは、エラーは不明です
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) 旧バージョンのTypeScriptにおけるerror型
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)
❓ よくある質問
Q
try/catch と Result パターンのどちらを使うべきですか?A この 2 つのアプローチは互いに排他的ではありません。
try/catchはJavaScript/TypeScriptにおける標準的な例外処理メカニズムであり、予測不可能な実行時エラー(ネットワーク障害やJSONの解析エラーなど)に適しています。一方、Resultパターンは予測可能なビジネスエラーに適しています。関数のシグネチャで「失敗の可能性がある」ことが明示されており、呼び出し側がそれを処理する必要があります。プロジェクト全体で一貫したスタイルを維持するようにしてください。Q なぜエラーは
Error ではなく catch unknown によって捕捉されるのですか?A JavaScript では任意の値をスローできるためです。つまり、
throw "message" も throw 42 も有効な値です。TypeScriptでは、catchでキャッチされる値がErrorのインスタンスであることを保証できないため、unknownを使用するのが最も安全な方法です。実際の開発では、スローされる値の99%はErrorオブジェクトであるため、instanceof Errorを使用することで型を絞り込むことができます。Q カスタムエラークラスは
super を呼び出す必要がありますか?A はい、必ず呼び出す必要があります。カスタムエラークラスが
Error を継承する場合、コンストラクタ内で super(message) を呼び出し、Error の message プロパティを初期化する必要があります。また、this.name を手動で設定することを推奨します。そうしないと、name はデフォルトで親クラスの名前に設定されます。Q
finally ブロック内のコードは戻り値に影響しますか?A
finally ブロック内の return ステートメントは、try/catch ブロック内の return ステートメントを上書きします。これはバグのよくある原因です。finallyブロックは、リソースのクリーンアップ(ファイルの閉じ処理、ロックの解放など)にのみ使用し、そこにreturn文を含めないことを推奨します。📖 まとめ
- JavaScript の組み込みエラーシステム — Error、TypeError、RangeError、およびその他のサブクラス
catchブロック内でinstanceofを使用すると、エラーの種類を絞り込み、特定のエラーのプロパティに安全にアクセスできますErrorを継承し、業務固有のプロパティ(fieldやstatusCodeなど)を追加したカスタムエラークラスを作成します。Resultパターンでは、エラーが戻り値の一部となります。これは、okとerrorを区別可能な和集合として扱います。catchブロック内のエラー型はunknownです。安全に処理するには、instanceofを使用するか、型ガードを使用してください。- 常に
Errorオブジェクトをスローしてください。文字列や数値をスローしてはいけません。
📝 練習問題
- 基本問題(難易度 ⭐):関数
safeParse(json: string): Result<object, Error>を記述してください。この関数は、JSON.parseをtry/catchブロックでラップし、成功した場合はsuccessを、失敗した場合はfailureを返すものです。 - 上級問題(難易度 ⭐⭐):基底クラス
AppErrorと、サブクラスDatabaseError(queryプロパティを持つ)およびAuthError(statusCodeプロパティを持つ)を定義してください。エラーの種類に応じて、異なるHTTPステータスコードとメッセージを返す関数を作成してください。 - 課題(難易度:⭐⭐⭐):
tryAsync<T>(fn: () => Promise<T>): Promise<Result<T, AppError>>を実装し、非同期操作で発生した例外を AppError システムに統一的に変換するようにします(ネットワークエラー、タイムアウトエラー、サーバーエラーを区別します)。失敗した場合に自動的に再試行を行うfetchWithRetry関数を記述してください。



