Fetch & ダウンロード進捗の追跡

概要: このチュートリアルでは、fetch() メソッドを使用してJavaScriptでサーバーからファイルをダウンロードし、ダウンロードの進捗状況を追跡する方法を学びます。

JavaScriptにおけるストリームの概要

JavaScriptでは、ストリームはネットワークを介して転送されるデータを処理する方法です。一度にすべてではなく、時間経過とともに処理されるバイトのシーケンスです。

ストリームを使用すると、利用可能なすべてのデータを待つよりも効率的な、データが到着した直後に処理できます。

通常、ストリームはデータをチャンクで処理します。チャンクは、ストリームで転送されるデータの小さな断片です。これは、大量のデータが時間経過とともに徐々に到着する場合に便利です。

たとえば、インターネット経由でビデオをストリーミングする場合、サーバーはビデオデータをチャンクで送信するため、ビデオファイル全体がダウンロードされる前にビデオの再生を開始できます。

fetch() メソッドを使用すると、ReadableStream を使用してファイルのダウンロード進捗状況を追跡できます。手順は次のとおりです。

ステップ1。fetch() を呼び出してダウンロードを開始します

const response = await fetch(url);Code language: JavaScript (javascript)

ステップ2。HTTPレスポンスのボディをチャンク単位で読み取るための ReadableStream (ソースストリームと呼ばれる)を作成します

const reader = response.body.getReader();Code language: JavaScript (javascript)

ステップ3。ソースストリームからデータを累積するための別のReadableStreamを作成します

const stream  =  new ReadableStream(start);Code language: JavaScript (javascript)

start は、ストリームを設定し、データがどのように流れるかを制御する関数です。

ステップ4。受信したチャンクのサイズを累積して進捗状況を追跡し、進捗状況をアプリのユーザーインターフェイス(UI)に更新します。

次の図は、ストリームを使用してダウンロード進捗状況を追跡する方法を示しています。

javascript tracking download progress

Fetchダウンロード進捗の例

ステップ1。新しいプロジェクトディレクトリ fetch-progress を作成します。

mkdir fetch-progress
cd fetch-progressCode language: JavaScript (javascript)

ステップ2。プロジェクトディレクトリに data.txt ファイルを作成します。このファイルをアプリからダウンロードします。

ステップ3。次のコードで index.html ファイルを作成します

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Download with Progress</title>
        <link rel="stylesheet" href="css/style.css">
        <script src="js/app.js" defer type="module"></script>
    </head>
    <body>
        <main>
            <h1>File Download Progress</h1>
            <button id="download-btn" class="download-btn">Download File</button>
            <div class="progress-container">
                <progress id="progress-bar" value="0" max="100">
                </progress>
                <div id="progress-text" class="progress-text">0%</div>
            </div>
        </main>
    </body>

</html>Code language: HTML, XML (xml)

ステップ4。css/style.css ファイルを作成します。

ステップ5。JavaScriptコードを保存するための js/app.js ファイルを作成します

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const downloadBtn = document.getElementById('download-btn');
downloadBtn.addEventListener('click', () => {
  const url = 'data.txt';
  downloadFile(url);
});

const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');

async function downloadFile(url) {
  downloadBtn.disabled = true;
  progressBar.value = 0;
  progressText.textContent = '0%';

  try {
    // fetch the file
    const response = await fetch(url);

    // check if the response is ok
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

    // get the total size of the file
    const contentLength = response.headers.get('content-length');

    // set the max value of the progress bar
    const totalSize = contentLength ? parseInt(contentLength, 10) : 0;

    // create the stream
    const reader = response.body.getReader();

    const stream = new ReadableStream({
      // start the stream
      async start(controller) {
        let loaded = 0;
        while (true) {
          // read the next chuck of data
          const { done, value } = await reader.read();
          //  simulate  network delay
          await delay(200);

          if (done) break;

          // calcualte the progress %
          loaded += value.length;
          const progress = totalSize ? (loaded / totalSize) * 100 : 0;

          // update the progressbar
          progressBar.value = progress;
          progressText.textContent = `${progress.toFixed(2)}%`;

          // send the data to the controller
          controller.enqueue(value);
        }
        // close the stream
        controller.close();
      },
    });

    // create the download link
    createDownloadLink(url, stream);

    // Update the progress text
    progressText.textContent = 'Download Complete!';
  } catch (error) {
    // update the progress text
    progressText.textContent = 'Download Failed!';
  } finally {
    // enable the download button
    downloadBtn.disabled = false;
  }
}

const createDownloadLink = async (url, stream) => {
  // Create a new blob URL
  const responseStream = new Response(stream);
  const blob = await responseStream.blob();
  // Create a link element, set the download attribute and trigger a download
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = url.split('/').pop();
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};Code language: JavaScript (javascript)

仕組み。

まず、指定されたミリ秒数だけネットワーク遅延をシミュレートする関数 delay を定義します。これは、ファイルが小さい場合やネットワーク速度が速い場合に進捗状況を確認できない場合に、進捗状況をテストするためのオプションです。

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));Code language: JavaScript (javascript)

次に、ダウンロードボタン要素を選択し、クリックイベントハンドラーを登録します

const downloadBtn = document.getElementById('download-btn');
downloadBtn.addEventListener('click', () => {
  downloadFile('data.txt');
});Code language: JavaScript (javascript)

クリックイベントハンドラー内で、downloadFile() 関数を呼び出して、アプリのルートディレクトリから data.txt ファイルをダウンロードします。

3番目に、ダウンロードするURLを受け入れる downloadFile() 関数を定義します

async function downloadFile(url)Code language: JavaScript (javascript)

downloadFile 関数では

1) progress-barprogress-text 要素を選択します

const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');Code language: JavaScript (javascript)

2) ダウンロードボタンを無効にし、プログレスバーの値をゼロに設定し、プログレステキストを0%に設定します

downloadBtn.disabled = true;
progressBar.value = 0;
progressText.textContent = '0%';Code language: JavaScript (javascript)

3) fetch() メソッドを使用してファイルのダウンロードを開始します

const response = await fetch(url);Code language: JavaScript (javascript)

4) リクエストが成功しなかった場合はエラーをスローします

if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);Code language: JavaScript (javascript)

5) リクエストが成功した場合は、HTTPレスポンスのヘッダーから content-length を取得してファイルサイズを取得します

const contentLength = response.headers.get('content-length');Code language: JavaScript (javascript)

6) content-length の値を整数に変換し、totalSize 変数に代入します

const totalSize = contentLength ? parseInt(contentLength, 10) : 0;Code language: JavaScript (javascript)

7) request.body プロパティの getReader() メソッドを呼び出して ReadableStream を取得します

const reader = response.body.getReader();Code language: JavaScript (javascript)

これは、ファイルからデータをチャンク単位で読み取るソースストリームです。

8) ソースストリームからデータを読み取る2番目のストリーム(カスタムストリーム)を作成します

const stream = new ReadableStream({
    // start the stream
    async start(controller) {
// ...Code language: JavaScript (javascript)

start() メソッドは、カスタムストリームが開始されると自動的に呼び出されます。ストリームを設定し、データがどのように流れるかを制御します。

9) これまでに読み取られたデータ量を追跡するための変数 loaded を初期化します

let loaded = 0;Code language: JavaScript (javascript)

10) ストリームが終了するまでストリームからデータを継続的に読み取る無限ループを作成します

while (true) {Code language: JavaScript (javascript)

11) ストリームから次のデータチャンクを読み取ります

const { done, value } = await reader.read();Code language: JavaScript (javascript)

read() メソッドは、2つのプロパティを持つオブジェクトに解決される Promise を返します

  • done: ストリームの読み取りが終了したかどうかを示すブール値。
  • value: 読み取られたデータのチャンク。

12) ストリームからデータを読み取るたびに200msのネットワーク遅延をシミュレートします

await delay(2000);Code language: JavaScript (javascript)

本番システムで使用する場合は、このコード行を削除する必要があることに注意してください。

13) ストリームから読み取るデータがなくなった場合、done が true に設定され、ループを終了します

if (done) break;Code language: JavaScript (javascript)

14) 現在のチャンクサイズ(value.length)を loaded 変数に追加して、読み取られたデータチャンクのサイズを累積します。

loaded += value.length;Code language: JavaScript (javascript)

15) データの総サイズ(totalSize)に対する、読み込まれたデータの割合を計算します。

const progress = totalSize ? (loaded / totalSize) * 100 : 0;Code language: JavaScript (javascript)

16) プログレスバーとプログレステキストを更新します

progressBar.value = progress;
progressText.textContent = `${progress.toFixed(2)}%`;Code language: JavaScript (javascript)

17) 現在のデータチャンク(value)をストリームに追加します

controller.enqueue(value);Code language: JavaScript (javascript)

18) すべてのデータが読み取られたらストリームを閉じます

controller.close();Code language: JavaScript (javascript)

19) createDownloadLink() 関数を呼び出してファイルをダウンロードします

createDownloadLink(url, stream);Code language: JavaScript (javascript)

20) プログレステキストを更新します

progressText.textContent = 'Download Complete!';Code language: JavaScript (javascript)

21) ストリーミング中にエラーが発生した場合は、progressText 要素にエラーメッセージを設定します

progressText.textContent = 'Download Failed!';Code language: JavaScript (javascript)

22) ストリーミングが成功したかどうかに関係なく、ダウンロードボタンを有効にします

} finally {
  // enable the download button
  downloadBtn.disabled = false;
}Code language: JavaScript (javascript)

ステップ6。Webブラウザーで index.html ファイルを起動します。次のページが表示されます

fetch download progress

index.html ファイルをWebサーバーでホストする必要があることに注意してください。または、VS Codeのliveserver 拡張機能を使用して index.html ファイルを起動することもできます。

Download File ボタンをクリックすると、Webサーバーから data.txt ファイルがダウンロードされ、進捗状況が表示されます。

ダウンロードが完了すると、コンピューター上のディレクトリにファイルを保存するように求められます

プロジェクトのソースコードをダウンロードします

ここをクリックしてプロジェクトのソースコードをダウンロードします

概要

  • ストリームを使用すると、最初のデータチャンクが到着するとすぐに、ネットワーク経由で転送されたデータをチャンク単位で処理できます。
  • ストリームデータを処理するには、ReadableStream APIを使用します。
このチュートリアルは役に立ちましたか?