JavaScript GeneratorとAsyncIteratorを用いたストリームデータ処理
JavaScript Frontend Backend Performance
結論
非同期イテレーションには async function* と for await...of を使い、finally で解放します。
// MDN公式仕様:AsyncGenerator によるストリームChunk取得とリソース解放
async function* fetchStreamChunks(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield decoder.decode(value, { stream: true });
}
} finally {
// ⭕ ループ途中 break やエラー発生時にも必ずクローズ処理を実行
reader.releaseLock();
}
}
MDN公式仕様:AsyncIterator プロトコルの評価比較
| 評価軸 | 配列一括読み込み (await fetch().json()) | AsyncIterator ストリーム処理 |
|---|---|---|
| メモリ消費量 | ❌ データサイズに比例して一気に肥大化 (OOMリスク) | ◯ 1Chunk分(数KB)のみで極小一定 |
| 画面初回表示(TTFB) | ❌ 全データ転送完了まで待機 | ◯ 最初のChunk到着時点で即表示開始 |
| 反復構文 | for (const item of array) | for await (const chunk of generator) |
実際に起こる事故:break や例外時におけるリソースリーク
MDN公式ドキュメント(AsyncGenerator Protocol)で規定されている通り、for await...of ループ処理の途中で break や例外スロー(throw)が発生した場合、JSエンジンはイテレータの .return() メソッドを呼び出してループを中断します。
この時、async function* 内で try...finally を記述していないと、WebソケットやDBカーソル、Fetchの getReader() ロックが解放されずに残り続け、サーバーがメモリリーク(OOM)を起こす障害が発生します。
実装手順
async function*構文で非同期ジェネレータ関数を宣言する- 内部のリソース生成・読み込み処理を
try { ... } finally { ... }ブロックで囲む yieldキーワードで1データ単位(Chunk)を呼び出し元にストリーミング返却する- 呼び出し側は
for await (const chunk of fetchStreamChunks(url))で安全にループ処理する