TypeScriptで非同期イテレーター(for await of)を活用する
生徒
「TypeScriptで、データの読み込みを待機しながら順番に処理する方法ってありますか?」
先生
「TypeScript(ES6/ESNext)では、for await ofという便利な仕組みを使って、非同期なデータを一つずつ取り出すことができます。」
生徒
「非同期イテレーター…難しそうですが、どうやって使うんですか?」
先生
「大丈夫ですよ。基本的な仕組みから、具体的な書き方まで順番に解説していきますね!」
1. 非同期イテレーター(for await of)とは?
プログラミングをしていると、「インターネットから複数のデータを順番に取得したい」とか「大きなファイルを少しずつ読み込みたい」という場面に出会います。こうした処理は時間がかかるため、JavaScriptやTypeScriptでは「非同期処理」(作業が終わるのを待たずに次の命令へ進む仕組み)として扱われます。
通常、配列(データのまとまり)をループで回すときは for...of を使いますが、中身が「いつ終わるかわからない非同期なデータ」の場合、普通のループではうまく扱えません。そこで登場するのが for await of です。これは、データが届くまで「ちょっと待って(await)」から次のループに進んでくれる、とても賢い繰り返し処理の仕組みです。
この機能はモダンなJavaScript(ES6以降)の機能を取り入れたTypeScriptで、型安全に、かつ直感的に書けるようになっています。初心者の方は、まず「時間差でやってくるデータを順番に処理するための特別なループ」だと覚えておきましょう。
2. なぜ for await of が必要なのか?
例えば、SNSの投稿を10件ずつ取得して表示する場合を考えてみてください。10件取得するのに1秒かかるとすると、合計で何秒かかるでしょうか。これらを一度に全部取得しようとすると、メモリを大量に消費したり、画面が固まったりすることがあります。
非同期イテレーターを使うと、1件取得できたら処理、また1件取得できたら処理……というように、データの準備ができたものから順番に、かつ安全に処理を進めることができます。これにより、パソコンやスマートフォンの負担を減らしつつ、スムーズな動作を実現できるのです。
ここで重要な用語を整理しておきましょう。
- イテレーター: データの集合から、要素を一つずつ順番に取り出す仕組みのこと。
- 非同期(Async): 処理が終わるのを待っている間、他の作業を止めることなく進める方式。
- Promise(プロミス): 「今はまだ結果がないけれど、将来的にデータかエラーを返します」という約束。
3. 非同期イテレーターの基本的な書き方
それでは、実際にTypeScriptでどのようにコードを書くのか見てみましょう。まずは、一定時間ごとに数字を返してくれる簡単な「非同期なデータの発生源」を作ってみます。
// 1秒待機するための関数
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// 非同期に数字を生成する関数(Async Generator)
async function* createAsyncNumbers() {
yield 1;
await sleep(1000); // 1秒待つ
yield 2;
await sleep(1000); // さらに1秒待つ
yield 3;
}
// メインの処理
async function runLoop() {
console.log("開始します...");
// for await...of を使って非同期データを取り出す
for await (const num of createAsyncNumbers()) {
console.log("取得した値:", num);
}
console.log("すべてのデータの処理が終わりました。");
}
runLoop();
実行結果は以下のようになります。
開始します...
取得した値: 1
(1秒待機)
取得した値: 2
(1秒待機)
取得した値: 3
すべてのデータの処理が終わりました。
このコードのポイントは、関数の前に async function* と星マークがついていることです。これを「非同期ジェネレーター」と呼び、yield を使うことで、値を一つずつ「はい、どうぞ」と渡すことができます。受け取る側は for await (const 変数 of ...) と書くだけで、非同期なやり取りを自動的に制御してくれます。
4. TypeScriptでの型定義とメリット
TypeScriptを使う最大のメリットは、流れてくるデータの「種類(型)」をはっきりさせることができる点です。パソコンが「次にくるのは数字だな」とか「次のは文字だな」と事前に知ることができるため、間違いを未然に防いでくれます。
非同期イテレーターを扱う場合、型としては AsyncIterable や AsyncGenerator という名前が使われます。難しく聞こえるかもしれませんが、「非同期で順番に渡せるデータのまとまり型」という意味です。
// ユーザー情報の型を定義
interface User {
id: number;
name: string;
}
// ユーザーを一人ずつ取得する非同期関数
async function* fetchUsers(): AsyncIterable<User> {
const users: User[] = [
{ id: 1, name: "田中" },
{ id: 2, name: "佐藤" },
{ id: 3, name: "鈴木" }
];
for (const user of users) {
await sleep(500); // 通信をシミュレーション
yield user;
}
}
async function displayUsers() {
// TypeScriptが user が User型 であることを知っているので安心
for await (const user of fetchUsers()) {
console.log(`ID: ${user.id}, 名前: ${user.name}`);
}
}
このように型をしっかり決めておくと、例えば user.name を user.namae と打ち間違えたときに、コードを実行する前にTypeScriptが「そんな項目はありませんよ!」と赤線で教えてくれます。これはプログラミング初心者にとって、非常に強力な味方になります。
5. 実践的な活用シーン:Web APIからのデータ取得
実際の開発現場では、インターネット上にある「Web API」から大量のデータを小分けにして取得するときにこの機能がよく使われます。例えば、検索結果が何百ページもある場合、一気に全部読み込むとパソコンのメモリが足りなくなってしまいます。
非同期イテレーターを使えば、「1ページ目を読み込んで表示」→「ユーザーが下までスクロールしたら2ページ目を読み込んで表示」といった処理が、非常にスッキリと記述できます。
また、for await of の中で try...catch という構文を使うことで、もし途中でインターネットが切れてしまった場合のエラー処理も簡単に書くことができます。
async function processDataSafe() {
try {
for await (const data of remoteDataSource()) {
console.log("処理中:", data);
}
} catch (error) {
console.error("途中でエラーが発生しました:", error);
}
}
このように、複雑になりがちな「待ち時間のある処理」を、まるで普通のループのようにシンプルに書けるのが for await of の魅力です。
6. 注意点と使いどころ
非常に便利な for await of ですが、使うときに一つだけ大きな約束事があります。それは、「async(非同期)関数の中」でしか使えないということです。
もし async がついていない普通の関数の中で for await of を書こうとすると、TypeScriptは「ここでは待機(await)できません!」と怒ってしまいます。非同期の魔法を使うには、その場所が非同期であることを宣言する必要があるのです。
また、全てのループをこれにする必要はありません。メモリにすでにある配列を処理するときは普通の for...of を使い、サーバーとの通信やファイルの読み込みなど、時間がかかる処理が絡むときだけこの機能を使うのが正解です。
7. ES6/ESNextとの連携について
TypeScriptは、最新のJavaScript(ESNextと呼ばれます)の機能をいち早く取り入れています。今回紹介した for await of もその一つです。TypeScriptで書いたコードは、最終的にブラウザが理解できるJavaScriptに変換されますが、TypeScriptの設定(tsconfig.json)を調整することで、古いブラウザでも動くように変換したり、最新の機能をそのまま活かしたりすることができます。
「最新の書き方(ESNext)」を「型(TypeScript)」で守りながら使う。これが現代のウェブ開発において、効率よくバグの少ないプログラムを作るためのスタンダードな手法になっています。