概要: このチュートリアルでは、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-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-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を使用します。