TypeScriptの非同期処理まとめ!エレガントで型安全なコードを書くコツ
生徒
「TypeScriptで、通信が終わるのを待ってから次の処理をしたいのですが、どうすればいいですか?」
先生
「それは非同期処理という技術を使います。Promiseやasync、awaitを使うと、型安全で綺麗なコードが書けますよ。」
生徒
「非同期処理って難しそうですね。具体的にはどのように使うんですか?」
先生
「基本さえ押さえれば大丈夫です。それでは、初心者の方向けに基本的な使い方から順番に見ていきましょう!」
1. 非同期処理とは何かを理解しよう
プログラミングの世界には、同期処理と非同期処理という二つの大きな動き方があります。通常、プログラムは上から下に向かって一行ずつ順番に実行されます。これを同期処理と呼びます。例えば、料理をするときに、お湯が沸騰するまでコンロの前でじっと動かずに待ち、沸騰してから次の作業に入るのが同期処理のイメージです。
一方で、非同期処理とは、時間のかかる作業を裏側で進めておき、その間に別の作業を進める仕組みのことです。お湯を沸かしている間に野菜を切る、といった効率的な動き方ができます。パソコンの世界では、インターネットを通じたデータの取得や、大きな画像の読み込みなどに時間がかかるため、この非同期処理が非常に重要になります。TypeScriptでは、この待ち時間をスマートに扱うための便利な道具が用意されています。
2. Promise(プロミス)の基本と仕組み
非同期処理を理解する上で最も大切なキーワードがプロミスです。直訳すると約束という意味になります。これは、今はまだ結果が出ていないけれど、将来的に結果を返すと約束された状態を指します。例えば、レストランで注文した後に渡される呼び出しベルのようなものです。ベルを持っている間は料理は完成していませんが、完成したらベルが鳴って知らせてくれますよね。プロミスには三つの状態があります。
一つ目は待機状態、二つ目は無事に成功して結果が返ってきた状態、三つ目は何らかの理由で失敗した状態です。TypeScriptでは、この結果に型をつけることができるため、どのようなデータが返ってくるのかをプログラムが事前に把握でき、ミスを防ぐことができます。これにより、開発者は安心してコードを書くことができるようになります。まずは、プロミスの基本的な書き方を確認してみましょう。
const myPromise = new Promise<string>((resolve, reject) => {
const success = true;
if (success) {
resolve("データの取得に成功しました!");
} else {
reject("エラーが発生しました。");
}
});
myPromise.then((message) => {
console.log(message);
}).catch((error) => {
console.error(error);
});
データの取得に成功しました!
3. asyncとawaitでコードを劇的に読みやすくする
先ほどのプロミスの書き方は少し複雑に見えたかもしれません。そこで登場するのが、アシンクとアウェイトという書き方です。これを使うと、非同期処理をまるで普通の同期処理のように、上から下へ流れるように書くことができます。アシンクは関数の前に付けて、この関数は非同期処理を含みますよと宣言するものです。アウェイトは、非同期処理が終わるまでそこで一時停止して待ってねという意味になります。
この二つは必ずセットで使います。アウェイトを使うことで、難しい入れ子構造を解消でき、コードがすっきりと美しくなります。これをエレガントなコードと呼んだりします。プログラミング未経験の方でも、この書き方なら何が行われているか直感的に理解しやすいはずです。特定の処理を待ってから次の処理に進むという流れが、言葉の通りに実現できるからです。
async function displayMessage() {
console.log("処理を開始します...");
const waitSeconds = (ms: number) => new Promise(res => setTimeout(res, ms));
await waitSeconds(2000); // 2秒間待機します
console.log("2秒経過しました。処理を完了します!");
}
displayMessage();
処理を開始します...
(2秒待機)
2秒経過しました。処理を完了します!
4. 型安全な非同期処理のメリット
TypeScriptを使う最大の利点は型安全です。型安全とは、データの種類をあらかじめ決めておくことで、間違った種類のデータを使おうとしたときに、実行する前にコンピュータが教えてくれる仕組みのことです。非同期処理においても、ネットワークから取得するデータが数値なのか、文字列なのか、あるいは複雑な人物の情報なのかを定義できます。
例えば、ユーザーの名前を取得する処理で、うっかり名前を数字として扱おうとすると、TypeScriptがエラーを出して止めてくれます。これにより、実際にアプリを動かしたときに突然動かなくなるようなトラブルを大幅に減らすことができます。大規模なプログラムになればなるほど、この型安全の恩恵は大きくなり、バグの少ない高品質なシステムを作ることが可能になります。初心者こそ、この型という概念を味方につけるのが上達の近道です。
5. 複数の非同期処理を同時に実行する方法
時には、いくつかの作業をバラバラに待つのではなく、まとめて一気に進めたい場合があります。例えば、三つの異なるサイトから情報を集める場合、一つずつ順番に待っていると時間がかかってしまいます。そんな時に便利なのが、プロミスオールという仕組みです。これを使うと、複数の非同期処理を同時にスタートさせ、全ての準備が整うのを一度に待つことができます。
これにより、全体の待ち時間を大幅に短縮できます。一つ一つの作業が終わるのを律儀に待つのではなく、並行して進めることで効率を上げるわけです。現代のウェブサイトやアプリでは、複数の場所からデータを読み込むのが当たり前ですので、この手法を知っておくと非常に役立ちます。ただし、どれか一つでも失敗すると全体が失敗扱いになるという特徴もあるので、使い所を見極めるのがポイントです。
async function fetchMultipleData() {
const task1 = new Promise<string>(res => setTimeout(() => res("データ1完了"), 1000));
const task2 = new Promise<string>(res => setTimeout(() => res("データ2完了"), 1500));
const task3 = new Promise<string>(res => setTimeout(() => res("データ3完了"), 500));
console.log("一斉に開始します");
const results = await Promise.all([task1, task2, task3]);
console.log("全てのデータが揃いました!");
console.log(results);
}
fetchMultipleData();
一斉に開始します
全てのデータが揃いました!
["データ1完了", "データ2完了", "データ3完了"]
6. 非同期処理でのエラーハンドリング
プログラミングにおいて、常に成功するとは限りません。インターネットの接続が切れていたり、サーバーがダウンしていたりすることもあります。そうした予期せぬ失敗に備えるのが、エラーハンドリングです。非同期処理では、トライとキャッチという構文をよく使います。トライのブロックの中に通常の処理を書き、もし途中でエラーが起きたら、キャッチのブロックに飛んでエラー処理を行うという流れです。
これを怠ると、エラーが起きた瞬間にアプリ全体が止まってしまう、いわゆる落ちる状態になってしまいます。ユーザーにエラーメッセージを表示したり、もう一度やり直すボタンを出したりするためには、この仕組みが不可欠です。丁寧なコードを書く人は、必ず失敗した時のことも考えてプログラムを組んでいます。TypeScriptなら、エラーの内容にも型を意識して、より堅牢な仕組みを構築できます。
async function getUserData(userId: number) {
try {
console.log("ユーザー情報を取得中...");
if (userId < 0) {
throw new Error("無効なIDです。");
}
// 成功した場合の処理
return { id: userId, name: "サンプルユーザー" };
} catch (error) {
// 失敗した場合の処理
console.error("エラーが発生しました。");
if (error instanceof Error) {
console.error("理由: " + error.message);
}
}
}
getUserData(-1);
ユーザー情報を取得中...
エラーが発生しました。
理由: 無効なIDです。
7. 実践的な活用シーンと注意点
非同期処理は、実際の開発現場では至る所で使用されています。例えば、スマートフォンアプリで画面を下に引っ張って更新する動作や、地図アプリでスクロールに合わせて新しい場所を表示する動きなどが代表的です。これらは全て、ユーザーの操作を邪魔しないように裏側で非同期処理が動いています。もし同期処理でこれらを行ってしまうと、通信が終わるまで画面がカチコチに固まってしまい、ユーザーは何も操作できなくなってしまいます。
ただし、注意点もあります。何でもかんでも非同期にすれば良いというわけではありません。処理の順番が重要な場合、適切な場所でアウェイトを入れないと、データがまだ無いのにそれを使おうとしてエラーになることもあります。また、複雑に非同期を組み合わせすぎると、プログラムの流れを人間が追うのが難しくなるコードの複雑化も招きます。シンプルさを保ちつつ、必要な部分にだけ効果的に取り入れるのが、プロ級のプログラマへの第一歩です。
8. 非同期処理をマスターするための学習ステップ
プログラミング未経験の方が非同期処理をマスターするには、焦らず一段階ずつ進むのが一番です。まずは、今回紹介したアシンクとアウェイトの書き方を暗記するくらい何度も書いてみてください。実際にコードを動かして、待機時間が発生する様子を自分の目で確認することが大切です。最初は意味が分からなくても、何度も繰り返すうちに、これは魔法の待ち時間なんだなと感覚的に掴めてくるはずです。
次に、自分なりに型を定義して、どのようなデータが返ってくるのかを想像しながらプログラムを作ってみましょう。TypeScriptはあなたの入力を常にチェックしてくれています。エラーが出たら、それはあなたが間違っているのではなく、TypeScriptがより良いコードにするためのヒントをくれているのだと捉えてください。そうして少しずつ小さな成功体験を積み重ねていくことで、気づいたときには型安全でエレガントなコードが自然と書けるようになっているでしょう。非同期処理を味方につければ、作れるものの幅が格段に広がりますよ。