摘要:在本教程中,您将学习如何使用 fetch()
方法在 JavaScript 中从服务器下载文件,并跟踪下载进度。
JavaScript 中流的介绍
在 JavaScript 中,流是一种处理通过网络传输的数据的方法。它是一系列随着时间的推移而处理的字节,而不是一次性处理所有字节。
通过使用流,您可以尽快处理到达的数据,这比等待所有可用数据更有效率。
通常,流以块的形式处理数据。块是流中传输的一小部分数据。当大量数据随着时间的推移逐渐到达时,这很有用。
例如,在通过互联网流式传输视频时,服务器会以块的形式发送视频数据,从而允许视频在整个视频文件下载完成之前开始播放。
fetch()
方法允许您通过使用 ReadableStream
来跟踪文件的下载进度。以下是步骤
步骤 1. 调用 fetch()
启动下载
const response = await fetch(url);
Code language: JavaScript (javascript)
步骤 2. 创建一个 ReadableStream
(称为源流)以分块读取 HTTP 响应的主体
const reader = response.body.getReader();
Code language: JavaScript (javascript)
步骤 3. 创建另一个 ReadableStream 以累积来自源流的数据
const stream = new ReadableStream(start);
Code language: JavaScript (javascript)
start
是一个函数,负责设置流并控制数据流经它的方式。
步骤 4. 通过累积接收到的块的大小来跟踪进度,并将进度更新到应用程序的用户界面 (UI)。
以下图表说明了如何使用流跟踪下载进度

获取下载进度示例
步骤 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. 创建 js/app.js
文件以存储 JavaScript 代码
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
文件。
第三,定义接受要下载的 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) 创建第二个流(自定义流)以从源流读取数据
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()
方法返回一个 Promise,它解析为一个具有两个属性的对象
done
:一个布尔值,指示流是否已完成读取。value
:读取的数据块。
12) 每次从流中读取数据时,模拟 200 毫秒的网络延迟
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 服务器上。或者,您可以使用 liveserver VS Code 扩展启动 index.html 文件。
如果您单击 下载文件
按钮,它将从 Web 服务器下载 data.txt
文件,并显示进度。

下载完成后,将提示您将文件保存到计算机上的目录中

下载项目源代码
总结
- 流允许您尽快处理通过网络传输的数据,并在第一个数据块到达时以块的形式处理数据。
- 使用 ReadableStream API 处理流数据。