TypeScriptはコンパイル時の型安全性を提供しますが、実行時のエラーハンドリングこそが堅牢なアプリケーションの要です。JavaScript時代からの try/catch は使えますが、TypeScript 4.0から catch 節の変数が unknown 型になり、正しく扱わないとコンパイルエラーになります。本記事では unknown 型の絞り込みから、カスタムErrorクラス・Result型パターン・非同期エラーまで、実務で使えるTypeScriptのエラーハンドリングを完全解説します。
catch節のunknown型を安全に扱う方法instanceof/typeofでエラー型を絞り込む方法- カスタム
Errorクラスの正しい型定義 - Result型(Either型)パターンで例外を使わないエラー処理
async/awaitでの非同期エラーハンドリングneverを使ったexhaustive checkの実装useUnknownInCatchVariablesオプションの使い方
1. catch節の unknown 型 — TypeScript 4.0以降の変化
TypeScript 4.0以前は、catch 節の変数は暗黙的に any 型でした。TypeScript 4.0以降、tsconfig.json で "useUnknownInCatchVariables": true(strict モードで自動有効)を設定すると、catch (e) の e は unknown 型になります。これにより、エラー変数を使う前に型を確認することが強制されます。
// TypeScript 4.0以前(any型): プロパティアクセスが型チェックなしで通る
try {
riskyOperation();
} catch (e) {
console.log(e.message); // any型なのでエラーにならないが危険
}
// TypeScript 4.0以降(useUnknownInCatchVariables: true)
try {
riskyOperation();
} catch (e) {
// e は unknown 型 → プロパティアクセス前に型チェック必須
console.log(e.message); // ❌ コンパイルエラー: Object is of type 'unknown'
}
1-1. エラー変数を安全に使う基本パターン
unknown 型の e を使うには、事前に型を絞り込む必要があります。最もシンプルなパターンは instanceof Error で確認することです。
try {
riskyOperation();
} catch (e) {
if (e instanceof Error) {
// この節では e は Error 型に絞り込まれる
console.error(e.message); // OK
console.error(e.stack); // OK
console.error(e.name); // OK
} else {
// Error でない場合(throw "string" など)
console.error("Unknown error:", String(e));
}
}
1-2. エラーメッセージを取り出すユーティリティ関数
どんなエラーでも文字列メッセージを取り出せるユーティリティ関数を作っておくと便利です。コードベース全体で再利用でき、型安全性も保てます。
/**
* 任意の値からエラーメッセージを取り出す
* unknown 型の catch 節の変数に対して使う
*/
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
if (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof (error as Record<string, unknown>).message === "string"
) {
return (error as Record<string, unknown>).message as string;
}
return String(error);
}
// 使用例
try {
await fetchData();
} catch (e) {
console.error("エラー:", getErrorMessage(e)); // 常に string を返す
}
throw する値は必ず Error インスタンスにするJavaScriptでは
throw "エラー文字列" や throw 404 のように文字列・数値もthrowできますが、stack トレースが取れず原因調査が困難になります。必ず throw new Error("メッセージ") か Error のサブクラスをthrowするようにしましょう。2. カスタム Error クラスの型定義
アプリケーションのエラーを分類するために、Error を継承したカスタムエラークラスを作ります。TypeScriptでは型定義が加わるため、エラーの種類に応じたプロパティを型安全に持てます。
2-1. 基本的なカスタムErrorクラス
// 基本的なカスタムErrorクラス
class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number = 500
) {
super(message);
// TypeScript + Babel環境での instanceof 問題を回避
this.name = this.constructor.name;
Object.setPrototypeOf(this, new.target.prototype);
}
}
// 使用例
try {
throw new AppError("ユーザーが見つかりません", "USER_NOT_FOUND", 404);
} catch (e) {
if (e instanceof AppError) {
console.error(`[${e.code}] ${e.message} (HTTP ${e.statusCode})`);
// → [USER_NOT_FOUND] ユーザーが見つかりません (HTTP 404)
}
}
2-2. エラー種別ごとにクラスを分ける
// ベースエラークラス
class AppError extends Error {
constructor(
message: string,
public readonly code: string
) {
super(message);
this.name = this.constructor.name;
Object.setPrototypeOf(this, new.target.prototype);
}
}
// 具体的なエラー種別
class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} が見つかりません`, "NOT_FOUND");
}
}
class ValidationError extends AppError {
constructor(
message: string,
public readonly field: string
) {
super(message, "VALIDATION_ERROR");
}
}
class NetworkError extends AppError {
constructor(
message: string,
public readonly statusCode: number
) {
super(message, "NETWORK_ERROR");
}
}
// 型ガードで絞り込み
function handleError(e: unknown): void {
if (e instanceof NotFoundError) {
console.warn("404:", e.message);
} else if (e instanceof ValidationError) {
console.error(`検証エラー [${e.field}]: ${e.message}`);
} else if (e instanceof NetworkError) {
console.error(`ネットワークエラー HTTP ${e.statusCode}: ${e.message}`);
} else if (e instanceof AppError) {
console.error(`アプリエラー [${e.code}]: ${e.message}`);
} else if (e instanceof Error) {
console.error("予期しないエラー:", e.message);
} else {
console.error("不明なエラー:", String(e));
}
}
Object.setPrototypeOf(this, new.target.prototype) が必要な理由TypeScriptを
target: ES5 でコンパイルすると、Error のサブクラスで instanceof が正しく動作しないバグがあります。Object.setPrototypeOf(this, new.target.prototype) をコンストラクタ内に追加することで、この問題を回避できます。target: ES2015 以降では不要ですが、安全のため付けておくことを推奨します。3. エラー型ガード関数の実装
instanceof のほかに、型ガード関数(is 構文)を使うと再利用性の高いエラー判定ロジックを書けます。特に、カスタムプロパティを持つオブジェクトエラーを判定するときに役立ちます。
// 型ガード関数の定義
function isError(value: unknown): value is Error {
return value instanceof Error;
}
function isAppError(value: unknown): value is AppError {
return value instanceof AppError;
}
// APIレスポンスのエラーオブジェクトを判定する型ガード
interface ApiErrorResponse {
error: string;
message: string;
statusCode: number;
}
function isApiErrorResponse(value: unknown): value is ApiErrorResponse {
return (
typeof value === "object" &&
value !== null &&
"error" in value &&
"message" in value &&
"statusCode" in value &&
typeof (value as ApiErrorResponse).error === "string" &&
typeof (value as ApiErrorResponse).message === "string" &&
typeof (value as ApiErrorResponse).statusCode === "number"
);
}
// 使用例
async function fetchUser(id: number) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
const body = await res.json();
if (isApiErrorResponse(body)) {
throw new NetworkError(body.message, body.statusCode);
}
throw new NetworkError("APIエラー", res.status);
}
return await res.json();
} catch (e) {
if (isError(e)) throw e; // Errorはそのまま再throw
throw new AppError(String(e), "UNKNOWN");
}
}
4. Result型パターン — 例外を使わないエラー処理
Rustや関数型言語では「エラーも戻り値として返す」Result型(Either型)パターンが標準的です。TypeScriptでも同じパターンを実装でき、例外によるコントロールフローの複雑化を防ぎ、エラーを戻り値として型安全に扱えます。特に複数のエラーケースを持つ関数で効果的です。
4-1. シンプルなResult型の定義
// Result型の定義
type Ok<T> = { success: true; data: T };
type Err<E> = { success: false; error: E };
type Result<T, E = Error> = Ok<T> | Err<E>;
// ヘルパー関数
const ok = <T>(data: T): Ok<T> => ({ success: true, data });
const err = <E>(error: E): Err<E> => ({ success: false, error });
// Result型を返す関数の例
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return err("0で割ることはできません");
return ok(a / b);
}
// 呼び出し側
const result = divide(10, 0);
if (result.success) {
console.log("結果:", result.data); // data は number 型
} else {
console.error("エラー:", result.error); // error は string 型
}
4-2. 非同期処理でのResult型
type AsyncResult<T, E = Error> = Promise<Result<T, E>>;
// APIコールをResult型で包む
async function fetchUserResult(id: number): AsyncResult<User, AppError> {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
return err(new NetworkError(`HTTP ${res.status}`, res.status));
}
const data = await res.json() as User;
return ok(data);
} catch (e) {
if (e instanceof AppError) return err(e);
return err(new AppError(getErrorMessage(e), "FETCH_ERROR"));
}
}
// 使用例: try/catch 不要で型安全なエラー処理
const result = await fetchUserResult(123);
if (!result.success) {
if (result.error instanceof NetworkError && result.error.statusCode === 404) {
console.warn("ユーザーが見つかりません");
} else {
console.error("エラー:", result.error.message);
}
return;
}
// success === true のブロックでは result.data は User 型
renderUser(result.data);
4-3. try/catch をResult型に変換するラッパー
// 同期関数をResult型に変換
function tryCatch<T>(fn: () => T): Result<T> {
try {
return ok(fn());
} catch (e) {
return err(e instanceof Error ? e : new Error(String(e)));
}
}
// 非同期関数をResult型に変換
async function tryCatchAsync<T>(fn: () => Promise<T>): AsyncResult<T> {
try {
return ok(await fn());
} catch (e) {
return err(e instanceof Error ? e : new Error(String(e)));
}
}
// 使用例: JSON.parse のエラーを安全に捕捉
const parsed = tryCatch(() => JSON.parse(rawInput));
if (!parsed.success) {
console.error("JSONパースエラー:", parsed.error.message);
} else {
processData(parsed.data);
}
// 使用例: fetch をResult型で扱う
const result = await tryCatchAsync(() => fetch("/api/data").then(r => r.json()));
5. 非同期処理のエラーハンドリング
async/await では try/catch で非同期エラーを捕捉できますが、Promise.all や Promise.allSettled の扱いに注意が必要です。
5-1. async/await の基本パターン
// ✅ 正しいパターン: await を try/catch で囲む
async function loadData(): Promise<Data> {
try {
const res = await fetch("/api/data");
if (!res.ok) {
throw new NetworkError(`HTTP ${res.status}`, res.status);
}
return await res.json() as Data;
} catch (e) {
if (e instanceof NetworkError) throw e; // 既知のエラーはそのままthrow
throw new AppError(getErrorMessage(e), "LOAD_DATA_ERROR");
}
}
// ❌ よくある間違い: await の前に try/catch を置く
async function badPattern() {
let data;
try {
const promise = fetch("/api/data"); // ここではエラーは起きない
} catch (e) { /* catch できない */ }
data = await promise; // await の後のエラーが catch されない
}
5-2. Promise.all と Promise.allSettled の使い分け
// Promise.all: 1つでも失敗すると全体が失敗
async function fetchAll() {
try {
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts(),
]);
return { users, posts };
} catch (e) {
// どちらが失敗したかは e の内容から判断
throw new AppError(getErrorMessage(e), "FETCH_ALL_ERROR");
}
}
// Promise.allSettled: 全件実行し、成功・失敗を個別に取得
async function fetchAllSettled(ids: number[]) {
const results = await Promise.allSettled(
ids.map(id => fetchUser(id))
);
const succeeded: User[] = [];
const failed: { id: number; reason: string }[] = [];
results.forEach((result, index) => {
if (result.status === "fulfilled") {
succeeded.push(result.value);
} else {
failed.push({ id: ids[index], reason: getErrorMessage(result.reason) });
}
});
return { succeeded, failed };
}
5-3. AbortController を使ったタイムアウト処理
// fetchのタイムアウトエラーを型安全に処理する
class TimeoutError extends AppError {
constructor(public readonly timeoutMs: number) {
super(`${timeoutMs}ms でタイムアウトしました`, "TIMEOUT_ERROR");
Object.setPrototypeOf(this, new.target.prototype);
}
}
async function fetchWithTimeout<T>(
url: string,
timeoutMs: number = 5000
): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new NetworkError(`HTTP ${res.status}`, res.status);
return await res.json() as T;
} catch (e) {
if (e instanceof Error && e.name === "AbortError") {
throw new TimeoutError(timeoutMs);
}
throw e;
} finally {
clearTimeout(timeoutId);
}
}
// 使用例
try {
const data = await fetchWithTimeout<User[]>("/api/users", 3000);
} catch (e) {
if (e instanceof TimeoutError) {
console.warn(`タイムアウト (${e.timeoutMs}ms)`);
} else if (e instanceof NetworkError) {
console.error(`ネットワークエラー HTTP ${e.statusCode}`);
}
}
6. never を使った Exhaustive Check(網羅性チェック)
ユニオン型の全ケースを switch で処理しているかどうかを、コンパイル時に保証するテクニックが Exhaustive Check です。never 型への代入を利用してケース漏れをコンパイルエラーにします。
type AppErrorType = "NOT_FOUND" | "VALIDATION_ERROR" | "NETWORK_ERROR" | "AUTH_ERROR";
// exhaustive check 用の関数
function assertNever(value: never): never {
throw new Error(`Exhaustive check failed: ${JSON.stringify(value)}`);
}
function getHttpStatus(errorType: AppErrorType): number {
switch (errorType) {
case "NOT_FOUND": return 404;
case "VALIDATION_ERROR": return 422;
case "NETWORK_ERROR": return 503;
case "AUTH_ERROR": return 401;
default:
// 全ケースが処理されていれば errorType は never 型
// 処理されていないケースがあればコンパイルエラー
return assertNever(errorType);
}
}
// AppErrorType に "RATE_LIMIT" を追加した場合:
// type AppErrorType = "NOT_FOUND" | ... | "RATE_LIMIT";
// → getHttpStatus の switch に "RATE_LIMIT" がないのでコンパイルエラーになる
// → ケース漏れをコンパイル時に発見できる
6-1. 判別可能なユニオン型とのExhaustive Check
type ApiError =
| { kind: "network"; statusCode: number }
| { kind: "validation"; fields: string[] }
| { kind: "auth"; reason: string }
| { kind: "notFound"; resource: string };
function formatApiError(error: ApiError): string {
switch (error.kind) {
case "network":
return `ネットワークエラー (HTTP ${error.statusCode})`;
case "validation":
return `入力エラー: ${error.fields.join(", ")}`;
case "auth":
return `認証エラー: ${error.reason}`;
case "notFound":
return `${error.resource} が見つかりません`;
default:
return assertNever(error); // 全ケース網羅済みのためコンパイルエラーにならない
}
}
判別可能なユニオン型の詳細については TypeScript 判別可能なユニオン型(Discriminated Unions)完全ガイド も参照してください。
7. エラーハンドリングに関連する tsconfig.json 設定
| オプション | 説明 | デフォルト | 推奨 |
|---|---|---|---|
useUnknownInCatchVariables |
catch 節の変数を unknown 型にする(strict 配下) |
false |
true(strict: true で自動有効) |
strictNullChecks |
null/undefined を明示的に扱うことを強制 |
false |
true(strict: true で自動有効) |
noUncheckedIndexedAccess |
配列・オブジェクトのインデックスアクセスに undefined を含める |
false |
true(明示的に設定が必要) |
strict |
上記を含む厳格な型チェックをまとめて有効化 | false |
true |
// tsconfig.json 推奨設定(エラーハンドリング関連)
{
"compilerOptions": {
"strict": true, // useUnknownInCatchVariables を含む全厳格オプション
"noUncheckedIndexedAccess": true, // arr[0] が T | undefined になる
"noImplicitReturns": true, // 全パスで return を強制
"noFallthroughCasesInSwitch": true // switch の fallthrough を禁止
}
}
7-1. noUncheckedIndexedAccess の効果
noUncheckedIndexedAccess を有効にすると、配列・オブジェクトへのインデックスアクセスの戻り値に undefined が含まれます。これにより、未チェックのインデックスアクセスが原因の TypeError をコンパイル時に防げます。
// noUncheckedIndexedAccess: true の効果
const items = ["apple", "banana", "cherry"];
// ❌ コンパイルエラー: items[0] は string | undefined
const first: string = items[0]; // NG
// ✅ undefined チェックが必要
const firstItem = items[0];
if (firstItem !== undefined) {
console.log(firstItem.toUpperCase()); // OK: string に絞り込まれる
}
// ✅ ?? でデフォルト値を指定
const safeFirst = items[0] ?? "default";
console.log(safeFirst.toUpperCase()); // OK: string
// オブジェクトでも同様
const map: Record = { a: 1, b: 2 };
const val = map["a"]; // number | undefined
if (val !== undefined) {
console.log(val * 2); // OK
}
8. エラーの再スロー・ラップ・変換パターン
8-1. エラーを別の型に変換してthrow
// 下位レイヤーのエラーを上位レイヤー向けに変換
async function getUserById(id: number): Promise<User> {
try {
return await db.users.findById(id);
} catch (e) {
// DB層のエラーをドメインエラーに変換
if (e instanceof DatabaseNotFoundError) {
throw new NotFoundError(`User(id=${id})`);
}
// 想定外のエラーはラップして再throw(原因を保持)
throw new AppError(
`ユーザー取得に失敗しました: ${getErrorMessage(e)}`,
"DB_ERROR"
);
}
}
8-2. エラーチェーン(cause プロパティ)
// ES2022 / TypeScript 4.6以降: Error の cause プロパティ
class ServiceError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options); // options.cause に元のエラーを渡せる
this.name = "ServiceError";
Object.setPrototypeOf(this, new.target.prototype);
}
}
async function processOrder(orderId: string): Promise<void> {
try {
await paymentService.charge(orderId);
} catch (e) {
// cause に元のエラーを保持してラップ
throw new ServiceError(
`注文 ${orderId} の支払い処理に失敗`,
{ cause: e } // cause プロパティに元エラーを保持
);
}
}
// catch側でエラーチェーンを辿る
try {
await processOrder("ORD-001");
} catch (e) {
if (e instanceof ServiceError) {
console.error("サービスエラー:", e.message);
console.error("原因:", e.cause); // 元のエラー
}
}
9. 実践例3本
実践例1:型安全なAPIクライアントの実装
Result型パターンとカスタムErrorクラスを組み合わせた、型安全なAPIクライアントの実装例です。エラーを戻り値として扱うため、呼び出し側でのエラー処理が型推論で補完されます。
// 型安全なAPIクライアント
class ApiClient {
constructor(private readonly baseUrl: string) {}
async get<T>(path: string): AsyncResult<T, NetworkError> {
try {
const res = await fetch(`${this.baseUrl}${path}`);
if (!res.ok) {
return err(new NetworkError(`GET ${path}: HTTP ${res.status}`, res.status));
}
return ok(await res.json() as T);
} catch (e) {
return err(new NetworkError(getErrorMessage(e), 0));
}
}
async post<T, B>(path: string, body: B): AsyncResult<T, NetworkError | ValidationError> {
try {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (res.status === 422) {
const data = await res.json() as { field: string; message: string };
return err(new ValidationError(data.message, data.field));
}
if (!res.ok) {
return err(new NetworkError(`POST ${path}: HTTP ${res.status}`, res.status));
}
return ok(await res.json() as T);
} catch (e) {
return err(new NetworkError(getErrorMessage(e), 0));
}
}
}
// 使用例
const api = new ApiClient("https://api.example.com");
const result = await api.get<User[]>("/users");
if (!result.success) {
if (result.error.statusCode === 401) {
redirectToLogin();
} else {
showError(result.error.message);
}
return;
}
displayUsers(result.data); // result.data は User[] 型
実践例2:フォームバリデーションのエラー集約
複数フィールドのバリデーションエラーを集約してResult型で返すパターンです。全フィールドのエラーをまとめて取得でき、UIへの反映が型安全に行えます。
interface FormData {
name: string;
email: string;
age: number;
}
type ValidationErrors = Partial<Record<keyof FormData, string>>;
function validateForm(data: FormData): Result<FormData, ValidationErrors> {
const errors: ValidationErrors = {};
if (!data.name.trim()) {
errors.name = "名前を入力してください";
} else if (data.name.length > 50) {
errors.name = "名前は50文字以内で入力してください";
}
if (!data.email.includes("@")) {
errors.email = "正しいメールアドレスを入力してください";
}
if (data.age < 0 || data.age > 150) {
errors.age = "0〜150の数値を入力してください";
}
if (Object.keys(errors).length > 0) {
return err(errors);
}
return ok(data);
}
// 使用例
const validation = validateForm({ name: "", email: "invalid", age: -1 });
if (!validation.success) {
const { name, email, age } = validation.error;
// 各フィールドのエラーメッセージを表示
if (name) showFieldError("name", name);
if (email) showFieldError("email", email);
if (age) showFieldError("age", age);
} else {
submitForm(validation.data); // validation.data は FormData 型
}
実践例3:エラーログ収集ミドルウェア
Express.jsのエラーハンドリングミドルウェアで、カスタムErrorクラスに基づいてHTTPレスポンスを構築し、ログ収集まで行う実践パターンです。(前提: npm install express と npm install -D @types/express を実行済み)
import { Request, Response, NextFunction } from "express";
// Expressエラーハンドリングミドルウェア
export function errorHandler(
error: unknown,
req: Request,
res: Response,
_next: NextFunction
): void {
// エラーの種別に応じてHTTPレスポンスを構築
if (error instanceof ValidationError) {
res.status(422).json({
error: "VALIDATION_ERROR",
message: error.message,
field: error.field,
});
return;
}
if (error instanceof NotFoundError) {
res.status(404).json({
error: "NOT_FOUND",
message: error.message,
});
return;
}
if (error instanceof AppError) {
// 既知のアプリエラー: 4xx/5xx をログに記録してレスポンス
console.error(`[AppError] ${error.code}: ${error.message}`);
res.status(500).json({
error: error.code,
message: error.message,
});
return;
}
// 想定外のエラー: 詳細をログに残してジェネリックな500を返す
const message = getErrorMessage(error);
console.error(`[UnhandledError] ${req.method} ${req.path}: ${message}`);
if (error instanceof Error) {
console.error(error.stack);
}
res.status(500).json({
error: "INTERNAL_SERVER_ERROR",
message: "サーバーエラーが発生しました",
});
}
10. まとめ:TypeScriptエラーハンドリング チートシート
| 場面 | パターン | ポイント |
|---|---|---|
catch 節でエラーを使う |
if (e instanceof Error) |
unknown 型を絞り込んでから使う |
| エラーメッセージを取り出す | getErrorMessage(e) ユーティリティ関数 |
Error・文字列・オブジェクト全ケースに対応 |
| アプリ固有のエラー分類 | class AppError extends Error |
Object.setPrototypeOf で instanceof を正しく動かす |
| 例外を使わずエラーを返す | Result<T, E> 型 |
ok(data) / err(error) のヘルパー関数で扱いやすく |
| 非同期エラーの一括処理 | Promise.allSettled |
全件実行して成功・失敗を個別に取得 |
| ケース漏れを防ぐ | assertNever(x: never) |
switch の全ケースを網羅したかコンパイル時に確認 |
| エラーの原因を保持 | new Error(msg, { cause: e }) |
TypeScript 4.6以降 / ES2022 の cause プロパティ |
| 非同期タイムアウト | AbortController + setTimeout |
AbortError を TimeoutError に変換 |
FAQ
QuseUnknownInCatchVariables を有効にしたら既存コードが大量にエラーになりました。移行方法は?
Acatch (e) の e に対するプロパティアクセスに型エラーが出ます。急ぎの場合は catch (e: any) と明示的に any を指定して一時回避できますが、根本解決は if (e instanceof Error) で絞り込むか、getErrorMessage(e) のようなユーティリティ関数を使う方法を採用してください。新規コードから段階的に修正することを推奨します。
QResult型パターンを使うと throw しないのでスタックトレースが残りませんか?
AResult型を返す関数の内部では try/catch で Error インスタンスをキャッチして返すため、スタックトレースは result.error.stack に保持されています。エラーを握りつぶさずに err(new Error(...)) の形でErrorインスタンスを保持すれば、後でスタックトレースを取り出せます。
QカスタムErrorクラスで instanceof が false になることがあります。
ATypeScriptを target: ES5 でコンパイルすると、Error を継承したクラスで instanceof が正しく動作しません。コンストラクタ内に Object.setPrototypeOf(this, new.target.prototype) を追加してください。target: ES2015 以上に変更することでも解決できます。
Qtry/catch と Result型はどちらを使うべきですか?
A用途によって使い分けます。「発生が稀な予期しないエラー」や「プログラムの続行が不可能なエラー」には try/catch、「業務ロジックで発生しうる想定内のエラー」(バリデーション失敗・リソース未発見など)にはResult型が適しています。APIの境界やサービス層ではResult型を使うと呼び出し側のエラーハンドリングが型安全になり、エラーを無視した実装をコンパイル時に防げます。
Qfinally は async/await でも正しく動きますか?
Aはい。async/await の try/catch/finally は同期処理と同じように動作します。finally 節は return・throw・正常終了のいずれでも必ず実行されます。ただし finally 内で return や throw をすると、try・catch の戻り値やエラーが上書きされるため、finally はリソース解放(clearTimeout・接続クローズ等)のみに使うのが原則です。
QTypeScriptのエラーと関係ある内部リンク記事はありますか?
A型の絞り込み(Type Narrowing)ではinstanceof・typeof・ユーザー定義型ガードを、判別可能なユニオン型ではエラー種別の型定義パターンを詳しく解説しています。また、非同期処理の型定義ではPromise・async/await の型についても解説しています。

