最近需要这么一个功能,记录一下吧。
/**
* 异步文件下载
*
* 该函数通过fetch API从指定URL下载文件,并允许自定义请求选项通过流式下载,计算下载进度,并在控制台中显示
* 它创建一个Blob对象,然后使用URL.createObjectURL方法创建一个临时URL,最后通过创建一个隐藏的<a>元素来触发下载
*
* @param {string} url - 文件的URL
* @param {string} fileName - 保存文件时的文件名
* @param {Object} options - 可选的请求选项,传递给fetch函数
*/
async function downloadFile(url, fileName, options = {}) {
try {
// 发起网络请求,获取资源
const response = await fetch(url, options);
// 检查HTTP响应状态
if (!response.ok) {
handleHttpError(response);
return;
}
// 获取Content-Length头部,以确定文件大小,没有Content-Length的情况下继续下载,但不显示进度
const contentLength = response.headers.get('content-length');
if (!contentLength) {
console.warn('Content-Length header is missing in the response.');
}
// 解析文件总大小
const total = contentLength ? parseInt(contentLength, 10) : null;
let loaded = 0;
// 记录下载开始时间
const startTime = Date.now();
// 获取响应体的读取器,用于流式读取数据
const reader = response.body.getReader();
// 获取Content-Type头部,用于创建Blob对象
const contentType = response.headers.get('Content-Type');
const blobParts = [];
// 循环读取数据,直到完成
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 累加已加载的数据大小
loaded += value.length;
// 将读取的数据块添加到数组中,用于后续创建Blob对象
blobParts.push(value);
// 计算并打印下载进度
if (total !== null) {
const progress = Math.floor((loaded / total) * 100);
console.log(`Downloading: ${progress}%`);
} else {
// 如果没有Content-Length,计算平均下载速度
const elapsedTime = Date.now() - startTime;
const speed = (loaded / 1024 / 1024) / (elapsedTime / 1000); // MB/s
console.log(`Downloaded: ${loaded / 1024 / 1024} MB, Average Speed: ${speed.toFixed(2)} MB/s`);
}
}
// 创建Blob对象,包含下载的文件内容
const blob = new Blob(blobParts, { type: contentType });
// 创建临时URL,用于触发下载
const objectUrl = window.URL.createObjectURL(blob);
// 创建隐藏的<a>元素,用于触发下载
const a = createDownloadLink(objectUrl, fileName);
a.click();
// 使用事件监听器处理下载完成后的清理工作
a.addEventListener('click', () => {
cleanupResources(a, objectUrl);
});
} catch (error) {
// 捕获并打印下载过程中可能发生的错误
console.error('Error downloading file:', error);
}
}
function handleHttpError(response) {
if (response.status >= 400 && response.status < 500) {
console.error(`Client error! status: ${response.status}`);
} else if (response.status >= 500) {
console.error(`Server error! status: ${response.status}`);
} else {
console.error(`Unexpected HTTP error! status: ${response.status}`);
}
}
function createDownloadLink(url, fileName) {
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = fileName;
document.body.appendChild(a);
return a;
}
function cleanupResources(a, objectUrl) {
// 确保资源正确释放
window.URL.revokeObjectURL(objectUrl);
document.body.removeChild(a);
}