TypeScriptにおけるtry-catchブロックとErrorオブジェクトの型
TypeScriptでtry-catchブロックを使用する際、catchブロックで受け取るError
オブジェクトの型がunknown
になる場合があります。これは、TypeScript 4.4以降の仕様変更によるものであり、従来のany
型とは異なる扱いが必要になります。
本記事では、この問題について分かりやすく解説し、具体的な解決策をいくつか紹介します。
問題点
従来のTypeScriptでは、try-catchブロックで受け取るError
オブジェクトの型はany
でした。これは、あらゆる種類のオブジェクトを受け取れる汎用的な型ですが、型安全性という観点では問題があります。
一方、TypeScript 4.4以降では、Error
オブジェクトの型はunknown
に変更されました。unknown
型はany
型よりも厳格な型であり、ある程度の型情報が推論されるものの、具体的な型までは不明な状態を表します。
この変更により、以下の様な問題が発生します。
- エラー処理のコードが冗長になる
- エラーの種類を判別しにくい
Error
オブジェクトのプロパティに安全にアクセスできない
解決策
この問題を解決するには、以下の3つの方法があります。
1 型ガードを利用する
typeof
演算子やinstanceof
演算子などを利用して、Error
オブジェクトの型をより具体的に絞り込むことができます。
try {
// 処理
} catch (error) {
if (typeof error === 'string') {
// 文字列エラーの場合の処理
} else if (error instanceof Error) {
// Errorオブジェクトの場合の処理
console.error(error.message);
} else {
// その他のエラーの場合の処理
}
}
2 型アサーションを利用する
as
キーワードを利用して、Error
オブジェクトを特定の型に断言することができます。ただし、この方法は型情報を正しく推論できていない場合に実行時エラーが発生する可能性があるため、注意が必要です。
try {
// 処理
} catch (error) {
const typedError = error as Error; // エラーをError型に断言
console.error(typedError.message);
}
3 カスタムエラー型を利用する
独自のエラー型を定義し、try-catchブロックでその型を捕捉することで、より詳細なエラー処理を行うことができます。
class MyError extends Error {
constructor(message: string) {
super(message);
}
}
try {
// 処理
} catch (error) {
if (error instanceof MyError) {
// MyError型のエラーの場合の処理
console.error(error.message);
} else {
// その他のエラーの場合の処理
}
}
上記の解決策に加えて、以下の点にも注意する必要があります。
- ライブラリ製のエラーハンドリングユーティリティを利用することで、より簡潔なコードを書くことができます。
- TypeScriptの設定ファイル
tsconfig.json
でstrict
オプションを有効にすることで、型チェックをより厳格にすることができます。
以下のコードは、Error
オブジェクトの型をstring
型とError
型のどちらかに絞り込む例です。
try {
const value = parseInt('文字列'); // 文字列を数値に変換
} catch (error) {
if (typeof error === 'string') {
console.error('文字列変換エラー:', error);
} else if (error instanceof Error) {
console.error(error.message);
} else {
console.error('予期せぬエラー:', error);
}
}
以下のコードは、Error
オブジェクトをMyError
型に断言する例です。
class MyError extends Error {
constructor(message: string) {
super(message);
}
}
try {
throw new MyError('MyErrorが発生しました');
} catch (error) {
const myError = error as MyError; // エラーをMyError型に断言
console.error(myError.message);
}
以下のコードは、独自のエラー型MyError
を定義し、try-catchブロックで捕捉する例です。
class MyError extends Error {
constructor(message: string) {
super(message);
}
}
try {
// 処理中にMyErrorをスロー
throw new MyError('MyErrorが発生しました');
} catch (error) {
if (error instanceof MyError) {
console.error('MyError:', error.message);
} else {
console.error('その他のエラー:', error);
}
}
- より詳細なエラー処理を行う場合は、ログ出力だけでなく、適切なアクションを実行するようにしてください。
- 上記のコードはあくまで例であり、実際の用途に合わせて適宜修正する必要があります。
汎用的なtry-catchブロックを作成したい場合は、型パラメーターを利用することで、受け取るエラーオブジェクトの型を柔軟に指定することができます。
function tryCatch<T extends Error>(action: () => void, onError: (error: T) => void) {
try {
action();
} catch (error) {
if (error instanceof T) {
onError(error);
} else {
// その他のエラー処理
}
}
}
const divide = (x: number, y: number): number => {
if (y === 0) {
throw new Error('0で割れません');
}
return x / y;
};
tryCatch(
() => divide(10, 0),
(error) => {
console.error(error.message); // '0で割れません'が出力される
}
);
Promise/asyncを利用する
非同期処理においては、Promiseやasync/await構文を利用することで、より簡潔かつエレガントなエラー処理を行うことができます。
const fetchData = async (url: string): Promise<string> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return await response.text();
};
(async () => {
try {
const data = await fetchData('https://example.com/data.json');
console.log(data);
} catch (error) {
console.error(error.message);
}
})();
サードパーティ製のライブラリを利用する
TypeScript向けのエラーハンドリングライブラリがいくつか存在します。これらのライブラリを利用することで、より洗練されたエラー処理を行うことができます。
- テストケースを書いて、エラー処理が正しく動作することを確認しましょう。
- エラーメッセージは、問題をデバッグしやすいように、詳細かつわかりやすく記述しましょう。
- 型推論を最大限に活用するために、できるだけ具体的な型を使用するように心がけましょう。
typescript try-catch