概要: このチュートリアルでは、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)に更新します。
次の図は、ストリームを使用してダウンロード進捗状況を追跡する方法を示しています。

Fetchダウンロード進捗の例
ステップ1。新しいプロジェクトディレクトリ fetch-progress
を作成します。
mkdir fetch-progress
cd fetch-progress
Code 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-bar
と progress-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
ファイルを起動します。次のページが表示されます

index.html
ファイルをWebサーバーでホストする必要があることに注意してください。または、VS Codeのliveserver 拡張機能を使用して index.html
ファイルを起動することもできます。
Download File
ボタンをクリックすると、Webサーバーから data.txt
ファイルがダウンロードされ、進捗状況が表示されます。

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

プロジェクトのソースコードをダウンロードします
ここをクリックしてプロジェクトのソースコードをダウンロードします
概要
- ストリームを使用すると、最初のデータチャンクが到着するとすぐに、ネットワーク経由で転送されたデータをチャンク単位で処理できます。
- ストリームデータを処理するには、ReadableStream APIを使用します。