概要
非同期処理の本質は「完了を待たずに制御を進める」こと。
反復処理の本質は「ひとつずつ処理する」こと。
この2つを統合するのが、for await...of
という構文だ。
Promiseでは「まとめて非同期」、
for await...of
では「ひとつずつ非同期」。
その違いを理解すると、非同期処理の設計力は一段階上のステージへ進化する。
対象環境
ES2018 以降(Node.js 10+ / モダンブラウザ)
for await...of
の基本構文
for await (const item of asyncIterable) {
// 非同期に1つずつ処理
}
-
asyncIterable
は非同期イテラブル(AsyncIterator)である必要がある -
Symbol.asyncIterator
を持つオブジェクト - 1つずつ
await
しながら処理を進める
対応するオブジェクト
オブジェクト例 | 対応 |
---|---|
通常の配列 | ❌ |
配列 of Promise | ❌ |
AsyncGenerator | ✅ |
ReadableStream(Web / Node.js) | ✅ |
自作で Symbol.asyncIterator 実装 | ✅ |
実例①:非同期ジェネレーターとの併用
async function* fetchData() {
yield await Promise.resolve('step1');
yield await Promise.resolve('step2');
yield await Promise.resolve('step3');
}
for await (const step of fetchData()) {
console.log(step); // 'step1' → 'step2' → 'step3'
}
→ ✅ 各ステップを順番に“待ってから処理”
実例②:Node.jsの fs.createReadStream
import * as fs from 'fs';
const stream = fs.createReadStream('./log.txt', { encoding: 'utf8' });
for await (const chunk of stream) {
console.log('chunk:', chunk);
}
→ ✅ ストリームの読み込みを逐次的に await 処理できる
実例③:Web APIとの逐次通信(Paginated API)
async function* fetchPages() {
let page = 1;
while (true) {
const res = await fetch(`/api/data?page=${page}`);
const data = await res.json();
if (data.length === 0) break;
yield* data;
page++;
}
}
for await (const item of fetchPages()) {
console.log('item:', item);
}
→ ✅ 「まとめてfetchしない」設計が可能に
→ ✅ ページ単位での中断やリトライにも強い
カスタム非同期イテレータの作り方
const customAsyncIterable = {
async *[Symbol.asyncIterator]() {
yield 'one';
await new Promise(res => setTimeout(res, 100));
yield 'two';
}
};
for await (const value of customAsyncIterable) {
console.log(value);
}
よくある誤解
❌ Promise配列には使えない
const promises = [Promise.resolve(1), Promise.resolve(2)];
for await (const p of promises) {
// ✅ 実は動くが “AsyncIterator” ではない
}
→ これは特殊ケースの挙動
→ 推奨される使い方ではない(読みやすさ・明示性に欠ける)
設計上の使いどころ
状況 |
for await...of を使うべきか? |
理由 |
---|---|---|
ストリーミング受信 | ✅ Yes | チャンクごとに処理できる |
非同期ジェネレーターで処理を分割したい | ✅ Yes | 柔軟な制御構造を構築できる |
複数Promiseを一括で処理したい | ❌ No |
Promise.all() のほうが適切 |
APIページネーション / キュー処理 | ✅ Yes | 非同期逐次処理に強い |
結語
非同期処理は「まとめて実行する」ものではない。
「順番に受け取り、順番に処理する」ものとして設計することができる。
- 一括で処理 →
Promise.all()
- 分割して処理 →
for await...of
この設計選択こそが、非同期処理を“支配する”ための鍵である。
for await...of
は、非同期処理に構造を与える構文であり、
リアクティブでスケーラブルな処理設計を可能にする武器である。