TypeScriptのawaitとthenの違いとは?非同期処理を初心者向けに徹底解説
生徒
「TypeScriptの勉強を始めたのですが、awaitとかthenという言葉が出てきて混乱しています。これらは何のために使うものなのですか?」
先生
「これらは非同期処理という、時間がかかる作業を効率よく進めるための仕組みで使われます。料理に例えると、お湯が沸くのを待っている間に野菜を切るような、段取りの管理ですね。」
生徒
「なるほど!でも、awaitとthenはどう使い分ければいいんでしょうか?」
先生
「どちらも結果を待つための道具ですが、書き方や見え方が違います。まずは基本的な考え方から、順番に見ていきましょう!」
1. 非同期処理とPromiseの基本を知ろう
プログラミングの世界には、処理が終わるまでに時間がかかる作業があります。例えば、インターネットから大きな画像データをダウンロードしたり、データベースから情報を探してきたりする作業です。これらを普通に実行すると、その作業が終わるまでプログラム全体が止まってしまい、画面が固まってしまうことがあります。これを防ぐのが非同期処理です。
TypeScriptやJavaScriptでは、この非同期処理の結果を約束する仕組みとしてPromise(プロミス)というものを使います。プロミスは直訳すると「約束」です。「今はまだデータがないけれど、将来必ず結果を返します」という予約票のようなものだと考えてください。この予約票の状態には、作業中の待機状態、無事に終わった成功状態、何らかの理由で失敗した失敗状態の三つがあります。
2. thenを使った従来の方法を理解する
まずは、昔から使われているthenという書き方を見てみましょう。これは、先ほどの予約票に対して「もし成功したら、次にこの作業をやってね」と予約を入れておく方法です。処理をつなげていくイメージなので、メソッドチェーンとも呼ばれます。
初心者の方にとって、この書き方は「この処理が終わったら、次のカッコの中を実行する」という流れが視覚的に分かれているので、最初は理解しやすいかもしれません。しかし、連続して何度も処理をつなげると、コードがどんどん右にズレていってしまい、読みづらくなるという欠点もあります。これをよくプログラミングの世界では「コールバック地獄」に近い状態と呼んだりします。
// 擬似的にデータを取得する関数
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("データの取得が完了しました!"), 2000);
});
};
// thenを使った書き方
console.log("処理を開始します");
fetchData().then((message) => {
console.log(message);
});
console.log("次の処理(データの到着を待たずに実行されます)");
処理を開始します
次の処理(データの到着を待たずに実行されます)
(2秒後)
データの取得が完了しました!
3. asyncとawaitでスッキリ書く方法
次に、最近の主流であるasyncとawaitについて解説します。これは、非同期処理をあたかも普通の処理と同じように、上から下へ順番に動いているように書ける魔法のような仕組みです。使い方は簡単で、関数の前にasyncと書き、時間がかかる処理の前にawaitと書くだけです。
awaitは日本語で「待機する」という意味です。文字通り、その一行の処理が終わるまで、次の行へ進むのを一時停止してくれます。これにより、複雑な段取りが必要なプログラムでも、小説を上から下に読むような自然な感覚でコードを書くことができるようになります。ただし、awaitは必ずasyncと書かれた関数の中でしか使えないというルールがあるので注意しましょう。
// 挨拶を返す非同期関数
async function sayHello() {
return "こんにちは、TypeScriptの世界へ!";
}
// awaitを使って結果を受け取る
async function main() {
console.log("メッセージを準備中...");
const result = await sayHello();
console.log(result);
console.log("表示が終わりました");
}
main();
メッセージを準備中...
こんにちは、TypeScriptの世界へ!
表示が終わりました
4. awaitとthenの決定的な違いとは?
では、これら二つの使い分けはどこにあるのでしょうか。一番の違いは、プログラムの流れの見た目です。thenを使うと、非同期処理が終わった後の世界が、カッコ内のブロックとして切り離されます。一方でawaitを使うと、非同期処理が終わるまでその場で立ち止まるため、変数の受け渡しが非常にスムーズになります。
また、エラーが発生したときの対処法も異なります。thenの場合は専用のcatchという命令を後ろにつなげますが、awaitの場合は、普通のプログラミングで使われるtry-catchという構文を使って、エラーを捕まえることができます。これにより、エラーが起きたときの処理も一箇所にまとめやすく、初心者の方でもミスを見つけやすくなります。
5. 実践的な使い方!複数の処理を順番に行う
実際の開発では、一つの処理が終わったら、その結果を使って次の処理を行う、という場面が多くあります。例えば、ユーザーのIDを調べてから、そのIDを使ってプロフィールの詳細を取得するようなケースです。これをawaitを使って書くと、非常にスッキリとまとまります。
もしこれをthenで書くと、入れ子構造が深くなってしまい、どこで何をしているのかが分かりにくくなります。初心者の方は、まずはawaitを使った書き方をマスターすることをおすすめします。現代の開発現場でも、基本的にはawaitを使って書くことが推奨されています。
// ユーザーを探す処理(1秒かかる)
const findUser = (id: number) => {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: id, name: "田中太郎" }), 1000);
});
};
// 投稿を取得する処理(1秒かかる)
const getPosts = (userName: string) => {
return new Promise((resolve) => {
setTimeout(() => resolve(`${userName}さんの投稿一覧を取得しました`), 1000);
});
};
async function displayData() {
console.log("検索を開始します...");
const user: any = await findUser(1); // ユーザーを待つ
console.log(`${user.name}さんが見つかりました`);
const posts = await getPosts(user.name); // 投稿を待つ
console.log(posts);
}
displayData();
検索を開始します...
田中太郎さんが見つかりました
田中太郎さんの投稿一覧を取得しました
6. どちらを使うべきか迷った時の判断基準
基本的には、常にasync/awaitを使うのが現代のスタンダードです。コードが読みやすくなり、後から修正するのも簡単だからです。しかし、どうしてもthenを使わなければならない場面も稀にあります。例えば、関数の外でサクッと一度だけ結果を受け取りたい場合や、古い形式のライブラリを使っている場合などです。
また、複数の処理を「同時に」走らせたい場合には、awaitを並べるのではなく、別の特別な命令(Promise.allなど)を組み合わせて使うこともあります。しかし、まずは「順番に処理を実行したいならawait」と覚えておけば間違いありません。焦らずに、一つひとつの処理がどう動いているかをイメージしながら練習していきましょう。
7. エラーハンドリングの重要性
非同期処理には失敗がつきものです。インターネットが切れていたり、サーバーが止まっていたりすると、プログラムはエラーを吐き出します。async/awaitを使う際は、必ずと言っていいほどtry-catchという構文をセットで使います。これは「もしエラーが起きたら、こっちの処理に逃がしてね」という合図です。
エラーへの対策をしておかないと、アプリが突然真っ白になったり、動かなくなったりしてユーザーが困ってしまいます。プログラミング未経験の方は難しく感じるかもしれませんが、「予期せぬトラブルへの保険」だと考えて、セットで書く癖をつけることが大切です。
// 失敗する可能性のある通信処理
const connectToServer = () => {
return new Promise((resolve, reject) => {
const success = false; // 今回はわざと失敗させます
setTimeout(() => {
if (success) {
resolve("接続成功!");
} else {
reject("サーバーに繋がらないようです。");
}
}, 1500);
});
};
async function startSystem() {
try {
console.log("システムを起動しています...");
const status = await connectToServer();
console.log(status);
} catch (error) {
console.log("【注意】エラーが発生しました!");
console.log("理由:" + error);
}
}
startSystem();
システムを起動しています...
【注意】エラーが発生しました!
理由:サーバーに繋がらないようです。
8. 非同期処理をマスターするためのステップ
最後に、学習のコツをお伝えします。非同期処理は、目に見えない時間の流れを制御するため、最初は頭の中が混乱しやすいトピックです。まずは、今回のサンプルコードを自分で書き写して、実際に動かしてみることから始めてください。コンソール画面に文字が出てくるタイミングを観察するだけでも、大きな学びになります。
「なぜこの文字が先に出るのか?」「なぜここで止まるのか?」という疑問を一つずつ解決していくことで、TypeScriptの力が着実に身についていきます。一歩ずつ進んでいけば、必ず使いこなせるようになりますので、自分のペースで楽しみながら学習を続けていきましょう!