【JS応用|豆知識】Fetch APIの隠れた強力な味方!Response.prototype.bodyでストリームデータを制御する

1. 導入:なぜResponse.prototype.bodyが重要なのか

Web開発において、fetch APIを使ってデータを取得する際、通常は response.json() や response.text() を使って一気にデータを取り出します。しかし、数メガバイト、あるいは数ギガバイトに及ぶ巨大なファイルを扱う場合、すべてのデータをメモリに展開するとブラウザの動作が重くなったり、最悪の場合はクラッシュしたりします。
Response.prototype.body は、データを「ストリーム(流れるデータ)」として扱うための入り口です。これを利用することで、データのダウンロード中に少しずつ処理を進めることが可能になり、メモリ効率を劇的に改善できます。

2. 基礎知識:ReadableStreamとは

Response.prototype.body が返すのは ReadableStream オブジェクトです。これは、ネットワークから届いたデータを「チャンク(断片)」ごとに読み取れるインターフェースです。
イメージとしては、蛇口から流れてくる水をバケツで少しずつ受けるようなものです。一度に全量をバケツに入れるのではなく、必要な分だけを取り出し、処理が終わったら次の分を受け取るという仕組みにより、少ないメモリで巨大なデータを扱えるようになります。

3. 実装・解決策

ストリームを読み取るには、getReader() メソッドを使用します。これにより、データを非同期に反復処理(イテレーション)できます。
基本的な手順は以下の通りです。
1. fetchでリクエストを投げる。
2. response.body からリーダーを取得する。
3. ループ内で read() を呼び出し、データが終了するまでチャンクを処理し続ける。

4. サンプルプログラム

以下は、取得したストリームからチャンクを読み取り、コンソールにバイト長を表示する実用的なコードです。

async function fetchStreamData(url) {
  const response = await fetch(url);

  // bodyが利用可能かチェック
  if (!response.body) {
    throw new Error('このブラウザはReadableStreamをサポートしていません');
  }

  // リーダーを取得
  const reader = response.body.getReader();
  let receivedLength = 0;

  while (true) {
    // done: データが完了したか, value: 今回読み込んだチャンク(Uint8Array)
    const { done, value } = await reader.read();

    if (done) break; // 読み込み完了

    receivedLength += value.length;
    console.log(`現在のチャンクサイズ: ${value.length} バイト`);
    console.log(`合計受信済み: ${receivedLength} バイト`);
  }

  console.log('すべてのデータの読み込みが完了しました。');
}

// 実行例
fetchStreamData('https://example.com/large-file.zip');

5. 応用・注意点

注意点:
・一度しか読めない: ストリームは「一度読み取ると空になる」性質があります。reader.read() を使って読み取った後、再度 response.json() を呼び出そうとするとエラーになるため注意してください。
・文字化け対策: チャンクは Uint8Array(バイナリ)で届きます。テキストとして結合したい場合は、TextDecoder クラスを併用してデコードする必要があります。

現場での活用:
この技術は、プログレスバー(ダウンロード進捗)の実装に最適です。Content-Length ヘッダーから総容量を取得し、今回のチャンクサイズと合わせることで、正確なパーセンテージをリアルタイムで表示することができます。巨大なファイルを扱うモダンなフロントエンド開発では、必須のテクニックと言えるでしょう。

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