使用Fetch请求跟踪文件上传进度
在前端开发中,文件上传是常见的业务场景,很多时候我们需要让用户感知到上传的进度,避免等待时的焦虑感。传统的文件上传进度跟踪通常使用XMLHttpRequest实现,因为其自带progress事件。而Fetch API作为更现代的网络请求方案,本身并不支持原生的进度事件,不过我们可以结合ReadableStream和TransformStream来实现Fetch请求下的文件上传进度跟踪。
核心实现思路
要实现Fetch上传进度跟踪,核心逻辑分为以下几个步骤:
获取需要上传的文件对象,计算文件总大小
创建
TransformStream,在流的处理过程中累加已经上传的字节数,计算进度比例使用Fetch API发送请求,将文件流通过
TransformStream处理后作为请求体通过回调函数或者事件将进度信息传递给外部,用于更新UI
完整实现代码
以下是一个完整的文件上传进度跟踪示例,包含前端页面和核心逻辑:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fetch文件上传进度跟踪</title>
</head>
<body>
<input type="file" id="fileInput">
<button id="uploadBtn">开始上传</button>
<div id="progressContainer" style="margin-top: 20px; width: 300px; height: 20px; background-color: #f0f0f0;">
<div id="progressBar" style="width: 0%; height: 100%; background-color: #4caf50;"></div>
</div>
<p id="progressText">未开始上传</p>
<script>
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
// 创建进度跟踪的TransformStream
function createProgressStream(totalSize, onProgress) {
let uploadedSize = 0;
return new TransformStream({
transform(chunk, controller) {
// 累加已上传的字节数,chunk是Uint8Array类型
uploadedSize += chunk.byteLength;
// 计算进度百分比
const progress = Math.round((uploadedSize / totalSize) * 100);
// 回调进度信息
onProgress(progress, uploadedSize, totalSize);
// 将chunk传递给下一个流
controller.enqueue(chunk);
}
});
}
// 处理文件上传
async function uploadFile(file) {
if (!file) {
alert('请先选择文件');
return;
}
const totalSize = file.size;
// 重置进度显示
progressBar.style.width = '0%';
progressText.textContent = '上传中:0%';
// 创建进度跟踪的流
const progressStream = createProgressStream(totalSize, (progress, uploaded, total) => {
// 更新进度条和文本
progressBar.style.width = `${progress}%`;
progressText.textContent = `上传中:${progress}%(${uploaded}/${total} 字节)`;
});
try {
// 发起Fetch请求,将文件流通过progressStream处理
const response = await fetch('https://www.ipipp.com/upload', {
method: 'POST',
// 将文件流管道到progressStream,再作为请求体
body: file.stream().pipeThrough(progressStream),
// 如果后端需要表单格式,可以调整headers,这里示例为二进制流上传
headers: {
'Content-Type': 'application/octet-stream'
}
});
if (response.ok) {
progressText.textContent = '上传完成';
alert('文件上传成功');
} else {
progressText.textContent = '上传失败';
alert('文件上传失败');
}
} catch (error) {
progressText.textContent = '上传出错';
alert(`上传过程出错:${error.message}`);
}
}
// 绑定上传按钮点击事件
uploadBtn.addEventListener('click', () => {
const file = fileInput.files[0];
uploadFile(file);
});
</script>
</body>
</html>代码说明
上述代码的核心部分在于createProgressStream函数,它创建了一个TransformStream实例:
TransformStream的transform方法会在每个数据块被处理时触发,我们可以通过chunk.byteLength获取当前数据块的大小,累加得到已上传的总字节数。通过传入的
onProgress回调函数,将计算得到的进度百分比、已上传字节数、总字节数传递给外部,用于更新页面的进度条和文本。在Fetch请求中,我们通过
file.stream().pipeThrough(progressStream)将文件的原始流经过进度跟踪流处理后再作为请求体,这样所有上传的数据都会经过进度统计逻辑。
兼容性与注意事项
需要注意以下几点:
TransformStream和ReadableStream的pipeThrough方法是较新的浏览器特性,如果需要兼容旧版浏览器,可能需要添加对应的polyfill。上述示例的上传地址为
https://www.ipipp.com/upload,实际使用时需要替换为真实的后端上传接口地址。如果后端要求使用
multipart/form-data格式上传文件,需要调整请求体的构造方式,比如使用FormData结合流处理,不过FormData本身不支持直接管道流,这类场景下可能需要额外封装,或者仍使用XMLHttpRequest实现更简便的进度跟踪。进度计算是基于已发送的数据块大小,实际网络传输中可能存在缓冲等情况,进度显示可能存在微小偏差,属于正常现象。
总结
虽然Fetch API本身没有原生的进度事件,但通过结合TransformStream对上传流进行拦截处理,我们可以很方便地实现文件上传的进度跟踪。这种方式既使用了现代的Fetch API,又满足了进度展示的业务需求,适合在现代浏览器环境下使用。