TypeScriptで非同期処理(async/await)のエラーハンドリングを完全攻略!初心者向け解説
生徒
「TypeScriptで通信などの待ち時間が発生する処理を作っているのですが、失敗したときに画面が止まってしまいます。どうすればいいですか?」
先生
「それは非同期処理におけるエラーハンドリングが必要な場面ですね。asyncやawaitを使っているときに、もしトラブルが起きてもプログラムが壊れないように守る方法があるんですよ。」
生徒
「エラーハンドリング?難しそうですね。プログラミング初心者でも理解できるでしょうか?」
先生
「大丈夫です!お料理の失敗をリカバリーするようなイメージで、基本から丁寧に解説していきますね。それでは、書き方を見ていきましょう!」
1. 非同期処理とエラーハンドリングの基本
プログラミングの世界では、インターネットからデータを取ってきたり、重たい計算をしたりするときに、終わるまで待つ必要があります。これを非同期処理と呼びます。TypeScriptでは、この待ち時間を分かりやすく書くためにasyncとawaitという言葉を使います。
しかし、インターネットの調子が悪かったり、指定したデータが見つからなかったりすると、処理は失敗してしまいます。この「失敗」のことをエラー、または例外と呼びます。エラーが起きたときに、プログラムが突然終了してしまわないように、あらかじめ対策をしておくことをエラーハンドリングと言います。
まずは、一番シンプルなエラーハンドリングの仕組みを学びましょう。これは、もし失敗しても予備の動きを用意しておく「保険」のような役割を果たします。パソコンの操作に慣れていない方でも、特定のルールに従って書くだけなので安心して進めてください。
2. try-catch構文の使い方を覚えよう
TypeScriptで非同期処理のエラーを捕まえるために最もよく使われるのが、try...catchという構文です。これは日本語に直訳すると「試す(try)」と「捕まえる(catch)」という意味になります。
使い方はとても簡単です。まず、エラーが起きるかもしれない処理をtryというブロックの中に書きます。もしその中でエラーが発生したら、即座にcatchというブロックに処理が飛びます。これにより、エラーが起きてもアプリ全体が止まることなく、ユーザーに「エラーが発生しました」と優しく伝えることができるのです。
それでは、具体的なプログラムの書き方を見てみましょう。以下の例では、わざとエラーを起こすような処理を書いて、それをどのように捕まえるかを表現しています。
async function checkData() {
try {
console.log("データの読み込みを開始します...");
// ここでエラーが発生したと仮定します
throw new Error("通信に失敗しました");
console.log("この行は実行されません");
} catch (error) {
console.log("エラーをキャッチしました!内容はこちら:");
console.log(error.message);
}
}
checkData();
データの読み込みを開始します...
エラーをキャッチしました!内容はこちら:
通信に失敗しました
3. ネットワーク通信での実戦的なエラー対策
次に、より実践的な例を考えてみましょう。例えば、外部のウェブサイトから情報を取ってくる関数を作成する場合です。ここでは、fetchという命令を使います。この命令は非同期処理の代表格です。
もしURLが間違っていたり、相手のサーバーがダウンしていたりすると、fetchは失敗します。このとき、try...catchがないと、プログラムはそこで動かなくなってしまいます。初心者の方が陥りやすい罠は、正常に動くときのことだけを考えてコードを書いてしまうことです。プロの世界では、むしろ「失敗したときにどうするか」を考えることの方が重要です。
また、TypeScriptではエラーの型を意識することも大切です。catchで受け取るエラーがどのような中身を持っているかを確認することで、より親切なエラーメッセージを表示できるようになります。次のコードでは、通信の結果を確認して、問題があれば意図的にエラーを発生させています。
async function getUserProfile() {
try {
// 架空のURLからデータを取得しようとします
const response = await fetch("https://api.example.com/user");
if (!response.ok) {
// サーバーから「見つからない」などの返事があった場合
throw new Error("ユーザー情報が見つかりませんでした");
}
const data = await response.json();
console.log("取得成功!", data);
} catch (e) {
// 通信自体ができなかった場合や、上記で投げたエラーを処理します
console.error("トラブル発生:", e.message);
}
}
getUserProfile();
4. throwを使って自分からエラーを通知する
プログラミングをしていると、コンピューターが自動的に出すエラーだけでなく、自分たちで決めたルールに合わない場合にエラーとして扱いたいことがあります。例えば、入力された数字がマイナスだった場合に「それはおかしいよ!」と知らせるような場面です。
このときに使うのがthrowというキーワードです。これは日本語で「投げる」という意味です。自分でエラーを作成して投げると、それをcatch側で受け取ることができます。非同期処理の中でも、チェック機能を自作するときには欠かせない技術です。
例えば、パスワードの長さをチェックする非同期の関数を考えてみましょう。短すぎる場合にエラーを投げることで、後続の処理を安全にストップさせることができます。このように、エラーハンドリングはプログラムの安全装置としての役割を担っているのです。
async function validatePassword(password: string) {
try {
console.log("パスワードの安全性を確認中...");
if (password.length < 8) {
// 条件に合わないのでエラーを投げる
throw new Error("パスワードが短すぎます。8文字以上にしてください。");
}
console.log("チェック完了!安全なパスワードです。");
} catch (err) {
console.log("警告:", err.message);
}
}
validatePassword("123");
5. finallyを使って必ず実行する処理を書く
エラーが発生しても、しなくても、最後には必ずやっておきたい処理というものがあります。例えば、「読み込み中」という表示を消したり、開いたファイルを閉じたりする作業です。これを実現するのがfinallyブロックです。
tryで試して、catchでエラーを捕まえたあと、一番最後に必ず通る場所がfinallyです。これを使うことで、プログラムの書き漏らしを防ぐことができます。初心者の方は、後片付けを忘れがちですので、このfinallyをセットで覚えるのが上達の近道です。
現実の生活に例えると、料理をした後に、美味しくできても(成功)、焦がしてしまっても(失敗)、最後に必ずキッチンを掃除する(finally)という流れと同じです。非常に理にかなった仕組みですよね。
async function processOrder() {
let isLoading = true;
console.log("処理を開始します。読み込み中フラグ:", isLoading);
try {
console.log("注文を確定しています...");
// 擬似的なエラー発生
throw new Error("在庫がありませんでした");
} catch (error) {
console.log("エラー対応:", error.message);
} finally {
// 成功しても失敗しても必ず実行される
isLoading = false;
console.log("全ての処理が終わりました。読み込み中フラグ:", isLoading);
}
}
processOrder();
6. TypeScriptでの型安全なエラーハンドリング
TypeScriptの大きな特徴は、データに「型(種類)」をつけられることです。しかし、実はcatchで受け取るエラー変数は、デフォルトでは何が来るかわからない状態(unknown型)になっています。これは、エラーというものがどこから飛んでくるか予測しづらいためです。
そこで、より正確にプログラムを書くためには、受け取ったエラーが本当にエラーオブジェクトなのかを確認する作業が必要になります。これを型ガードと呼びます。instanceof Errorという書き方を使うことで、それが標準的なエラーなのかを判別できます。
少し難しく感じるかもしれませんが、「届いた荷物の中身が本当に頼んだものか確認してから開ける」ようなものだと思ってください。このひと手間を加えるだけで、TypeScriptの力を最大限に引き出し、バグの少ない頑丈なプログラムを書くことができるようになります。
7. 複数の非同期処理をまとめて扱うときのエラー
実際の開発では、一つの処理だけでなく、複数のことを同時に行いたい場合があります。例えば、ユーザーの名前と、投稿一覧を同時に読み込むようなケースです。これにはPromise.allという便利な機能を使います。
しかし、この便利な機能には注意点があります。複数の処理のうち、一つでもエラーが発生すると、全体が失敗したとみなされてしまいます。これを防ぐためには、個別の処理ごとにエラーハンドリングを行うか、あるいは全ての結果を配列として受け取れる別の機能を使う必要があります。
複数の処理を同時に回すのは、お祭りの屋台で複数の料理を同時に注文するようなものです。一つでも材料が足りなくて作れないと、注文全体が止まってしまうかもしれませんよね。そうならないための工夫も、エラーハンドリングの大切な要素です。
8. 初心者が間違えやすいポイントと解決策
最後に、学習を始めたばかりの方がよく間違えるポイントをまとめます。一番多いのは、awaitを付け忘れることです。awaitがないと、処理が終わるのを待たずに次の行へ進んでしまうため、エラーを捕まえるタイミングを逃してしまいます。
また、async関数の外でエラーを捕まえようとして失敗することもあります。非同期処理は、通常の処理とは時間の流れが違うため、適切な場所で待機してあげることが肝心です。エラーが発生したときに、コンソール(開発者用の画面)に赤い文字が出ても驚かないでください。それは、プログラムがあなたに「ここを直してほしい」と伝えているメッセージなのです。
エラーは敵ではなく、プログラムをより良くするためのヒントです。今回学んだtry...catchを駆使して、ユーザーにとって使いやすい、壊れにくいアプリケーションを目指していきましょう。一歩ずつコードを書いて動かしてみることが、何よりの勉強になります。