【JS応用|実務向け】ReadableStream APIで実現する、巨大データ処理の最適化とメモリ効率向上

1. 導入

現代のフロントエンド開発において、巨大なJSONデータの取得や、リアルタイムでのログ出力、大容量ファイルのアップロードなどを扱う機会が増えています。従来のfetch APIでレスポンス全体を一度にメモリへ読み込む手法は、メモリ不足によるブラウザのクラッシュや、ユーザーの待ち時間の増大を招くリスクがありました。ReadableStream APIを活用することで、データを「チャンク(断片)」単位で逐次処理することが可能となり、メモリ消費を最小限に抑えながら、ストリーミングによる高速なレスポンス体験を提供できるようになります。

2. 基礎知識

ReadableStreamとは、データのストリーム(流れ)を表現するWeb APIです。これを利用することで、ネットワークからデータが到着するたびに、その一部を順次処理できます。
関連する主要な概念は以下の通りです。
・チャンク(Chunk): ストリームから読み込まれるデータの断片。
・リーダー(ReadableStreamDefaultReader): ストリームからデータを読み出すためのインターフェース。
・読み込み完了(Done): ストリームにこれ以上データがないことを示すフラグ。
これらの仕組みにより、サーバーからの応答を待ち切る前に処理を開始できるため、UIの応答性を大幅に改善できます。

3. 実装/解決策

ReadableStreamを扱う基本手順は、レスポンスのbodyからgetReaderを取得し、ループ処理でチャンクを一つずつ処理することです。以下のポイントが重要です。
・非同期ループ(async/await): reader.read()を繰り返し呼び出し、doneがtrueになるまで待機します。
・Uint8Arrayのデコード: 受信したデータはバイナリ形式(Uint8Array)であるため、TextDecoderを使用して文字列に変換します。

4. サンプルプログラム

以下は、大規模なテキストファイルをストリーミングで読み込み、コンソールに逐次出力する実用的なコードです。

async function fetchStream(url) {
  try {
    const response = await fetch(url);
    
    // ストリームが存在しない場合はエラー
    if (!response.body) throw new Error('ReadableStreamがサポートされていません');

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let result = '';

    while (true) {
      // チャンクを取得
      const { done, value } = await reader.read();

      // ストリームの終了判定
      if (done) break;

      // バイナリデータを文字列に変換して処理
      const chunk = decoder.decode(value, { stream: true });
      result += chunk;
      console.log('受信した断片:', chunk);
    }

    console.log('全ての受信が完了しました');
  } catch (error) {
    console.error('ストリーミング処理中にエラーが発生しました:', error);
  }
}

// 実行例: fetchStream('https://example.com/large-data.txt');

5. 応用・注意点

実務でReadableStreamを扱う際に注意すべき点がいくつかあります。
・TextDecoderのオプション: 上記サンプルコードの{ stream: true }は重要です。マルチバイト文字(日本語など)がチャンクの境界で分断されても、適切に連結してデコードするための必須設定です。
・バックプレッシャー(Backpressure): ストリームの読み込み速度が処理速度を上回るとメモリが圧迫されます。もし処理が重い場合は、Promiseを使用して、処理が完了するまで次のread()を待機させる制御が必要です。
・エラーハンドリング: ネットワークが途中で切断された場合、reader.read()は例外を投げます。try-catchブロックで確実に捕捉し、UI側で「再試行」ボタンを表示するなどのUX設計を忘れないようにしましょう。

タイトルとURLをコピーしました