TypeScriptで非同期処理とループを極める!for await ofの使い方完全ガイド
生徒
「TypeScriptで、インターネットからデータを順番に取得したいのですが、ループを使うと順番がバラバラになったり、エラーが出たりして上手くいきません。」
先生
「それは非同期処理とループの組み合わせ方がポイントですね。TypeScriptには、一つずつの処理が終わるのを待ってから次に進むための、for await ofという便利な仕組みがあるんですよ。」
生徒
「フォー・アウェイト・オブですか?難しそうですが、私にも使いこなせますか?」
先生
「大丈夫です。基本的な考え方から、実際の書き方まで、一つずつ丁寧に解説していきますね。これを使えば、複雑な処理もスッキリ書けるようになりますよ!」
1. 非同期処理とPromiseの基本を知ろう
プログラミングの世界には、非同期処理という考え方があります。これは、ある処理が終わるのを待たずに、次の処理を進めてしまう仕組みのことです。例えば、大きなファイルをダウンロードしている間に、他の文字入力ができるようなイメージです。しかし、時には「ダウンロードが終わってから、そのファイルを開く」というように、順番を守らせたい場合があります。その時に使うのがPromiseです。
Promiseは、未来のどこかで処理が完了することを約束する入れ物のようなものです。このPromiseをより簡単に、まるで普通の順番通りのプログラムのように書けるようにしたのが、asyncとawaitです。asyncは関数を非同期にすることを宣言し、awaitはその処理が終わるまで一時停止して待つという意味になります。これらを理解することが、今回のテーマであるループ処理の土台となります。
2. 配列と非同期処理を組み合わせる難しさ
通常、配列の中身を一つずつ取り出すときは、forEachやfor...ofといったループを使います。しかし、これらの中に非同期処理を単純に入れてしまうと、問題が発生することがあります。例えば、1秒待ってから文字を表示する処理を5回繰り返したい場合、普通のループだと5回分がほぼ同時にスタートしてしまい、順番が守られなかったり、全ての処理が終わる前に次のコードが実行されてしまったりします。
初心者が特につまずきやすいのが、forEachの中でawaitを使おうとすることです。実は、forEachは非同期処理を待つようには作られていません。そこで登場するのが、今回の主役であるfor await ofです。これは、非同期的なデータの集まりを、一つずつ確実に終わるのを待ってから次へ進めてくれる、非常に強力な味方なのです。
3. for await ofの基本的な書き方
それでは、実際にどのようにコードを書くのか見ていきましょう。この構文は、非同期処理を返すデータの列を順番に処理するために設計されています。まずは、単純な数値の配列を、一秒ごとに表示する簡単なプログラムを作成してみます。ここでは、指定した時間だけ処理を止める関数を用意して、それをループの中で呼び出します。
// 待機処理を作る関数(ミリ秒指定)
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function processNumbers() {
const numbers = [10, 20, 30];
console.log("処理を開始します");
// 一つ一つの処理が終わるのを待ってから次へ進むループ
for await (const num of numbers) {
await sleep(1000); // 1秒待機
console.log("数値を出力:", num);
}
console.log("すべての処理が完了しました");
}
processNumbers();
実行結果は以下のようになります。1秒ごとにゆっくりと数字が表示されるのが確認できるはずです。
処理を開始します
数値を出力: 10
数値を出力: 20
数値を出力: 30
すべての処理が完了しました
4. 複数のPromiseを順番に実行する実用例
実際の開発では、複数のインターネット上のデータを順番に取得したいケースがよくあります。例えば、ユーザーのIDリストがあって、それぞれのプロフィール情報を一人ずつ順番に取得して画面に表示する場合などです。もし一斉にアクセスしてしまうと、相手のサーバーに負担をかけてしまうかもしれませんが、順番に処理すれば安全です。ここでは、複数のPromiseを格納した配列を、順番に処理する例を紹介します。
// ダミーのデータ取得関数
async function fetchUserData(id: number) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: id, name: "ユーザー" + id });
}, 500);
});
}
async function showUsers() {
// Promiseを返す関数のリスト
const userIds = [1, 2, 3, 4];
console.log("ユーザー情報の取得を開始します...");
for await (const id of userIds) {
const user: any = await fetchUserData(id);
console.log("取得完了:", user.name);
}
}
showUsers();
このように、配列内のデータを使って非同期関数を呼び出す際、for await ofを使うと、前の人のデータ取得が終わるまで次の人の処理を待機してくれます。これにより、プログラムの流れが非常に追いやすくなります。
5. ジェネレーター関数と組み合わせる高度な技
さらに一歩進んだ使い方として、ジェネレーター関数というものと組み合わせる方法があります。ジェネレーターは、値を一度に全部返すのではなく、必要に応じて一つずつ生成して返してくれる特別な関数です。これに非同期を組み合わせた「非同期ジェネレーター」を使うと、膨大なデータや、いつ終わるかわからないストリーミングデータなどを効率よく扱うことができます。用語解説として、yieldというキーワードは「値を一時的に外に出す」という意味だと覚えておきましょう。
// 非同期ジェネレーター関数
async function* asyncNumberGenerator() {
let i = 0;
while (i < 3) {
await new Promise(resolve => setTimeout(resolve, 800));
yield i++; // 値を生成して一時停止
}
}
async function runGenerator() {
console.log("ジェネレーターを開始します");
// 非同期イテレータをループで回す
for await (const value of asyncNumberGenerator()) {
console.log("生成された値:", value);
}
console.log("終了しました");
}
runGenerator();
実行結果は以下のようになります。少しずつデータが作られては、ループで処理されていく様子がわかります。
ジェネレーターを開始します
生成された値: 0
生成された値: 1
生成された値: 2
終了しました
6. エラーが発生した時の対処法
プログラムにエラーはつきものです。非同期処理のループ中にエラーが起きると、そこで全体の処理が止まってしまうことがあります。これを防ぐために、try...catchという構文を使いましょう。これは「もしエラーが起きたら、こっちの処理をしてね」と指示を出すためのものです。ループの中にこのブロックを入れることで、一つの処理が失敗しても、次の処理を継続させることが可能になります。
async function safeProcess() {
const tasks = ["タスクA", "失敗タスク", "タスクC"];
for await (const task of tasks) {
try {
console.log("実行中:", task);
if (task === "失敗タスク") {
throw new Error("何かがおかしいです!");
}
// 正常な処理をここに書く
} catch (error) {
console.log("エラーをキャッチしました:", error.message);
// エラーが起きても、次のループへ進みます
}
}
console.log("全工程が終了しました");
}
safeProcess();
このように、try...catchを適切に配置することで、信頼性の高いプログラムを作成することができます。特にインターネット通信などは不安定なこともあるため、エラー対策は必須の技術と言えるでしょう。
7. for await ofを使うべき場面と注意点
非常に便利なこの構文ですが、何でもかんでも使えば良いというわけではありません。最大の利点は順番を守ることです。しかし、もし「順番はどうでもいいから、とにかく全部早く終わらせたい」という場合は、Promise.allという別の方法を使ったほうが高速です。これは、全ての処理を一斉にスタートさせて、全部終わるのを待つというやり方です。
一方で、一つの処理の結果を次の処理で使いたい場合や、サーバーへの同時アクセス数を制限したい場合には、間違いなくfor await ofが最適です。パソコンを初めて触る方にとっては、最初は難しく感じるかもしれませんが、「順番に、確実に」というキーワードを意識して、まずは簡単な配列から試してみてください。一歩ずつコードを書いて実行結果を確認することで、必ず理解が深まっていきます。