如何实现不加载全部文件块到浏览器内存的分块下载?
解决方案:流式分块下载,避免内存耗尽
核心思路是利用浏览器的ReadableStream API逐块处理文件数据,无需将全部内容加载到内存中再触发下载。具体实现如下:
1. 流式分块处理逻辑
创建可读流,在流的读取周期内逐次请求文件块,解码Base64数据后推送到流中,直到所有块读取完成。
完整代码实现
async function streamDownloadFile(fileHandle, chunkSize, fileName) { let isDone = false; // 构建可读流,逐块拉取数据 const stream = new ReadableStream({ async pull(controller) { if (isDone) { controller.close(); return; } try { // 组装XML请求体 const requestXml = ` <Request> <Function>cloud::readFile</Function> <ArgList> <Argument name="fileHandle">${fileHandle}</Argument> <Argument name="size">${chunkSize}</Argument> </ArgList> </Request>`; // 发送分块请求 const response = await fetch('/your-api-endpoint', { method: 'POST', body: requestXml, headers: { 'Content-Type': 'text/xml' } }); if (!response.ok) throw new Error(`请求失败:${response.status}`); // 解析XML响应 const responseXml = await response.text(); const xmlDoc = new DOMParser().parseFromString(responseXml, 'text/xml'); const bufferBase64 = xmlDoc.querySelector('Result[name="buffer"]').textContent; const status = xmlDoc.querySelector('Result[name="status"]').textContent; const bytesRead = parseInt(xmlDoc.querySelector('Result[name="size"]').textContent); // 判断是否读取完成 if (status !== 'success' || bytesRead === 0) { isDone = true; controller.close(); return; } // Base64解码为二进制数组 const binaryStr = atob(bufferBase64); const uint8Array = new Uint8Array(binaryStr.length); for (let i = 0; i < binaryStr.length; i++) { uint8Array[i] = binaryStr.charCodeAt(i); } // 将当前块推入流 controller.enqueue(uint8Array); } catch (err) { controller.error(err); } } }); // 将流转换为可下载的Blob对象 const blob = await new Response(stream).blob(); const downloadUrl = URL.createObjectURL(blob); // 触发浏览器下载 const a = document.createElement('a'); a.href = downloadUrl; a.download = fileName; a.click(); // 下载完成后释放资源 setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000); }
2. 调用示例
// 按1MB分块下载文件 streamDownloadFile('your-open-file-handle', 1024 * 1024, 'target-file-name.ext');
关键注意事项
- 分块顺序保证:确保
cloud::readFile接口是按连续偏移量返回数据的,如果接口需要显式指定偏移参数,需在请求的ArgList中添加<Argument name="offset">${currentOffset}</Argument>并维护偏移量变量。 - 资源清理:必须调用
URL.revokeObjectURL释放临时URL,避免内存泄漏。 - 错误扩展:可根据业务需求添加重试机制、下载中断逻辑或进度提示。
方案优势
- 内存占用始终控制在单个分块的大小(如1MB),不会因为大文件累积内存导致浏览器崩溃。
- 浏览器直接将流数据写入下载缓存,无需在内存中存储完整文件内容。
内容的提问来源于stack exchange,提问作者Mohamed Rifkan
相关产品推荐
相关产品推荐

