You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用JavaScript分块下载30GB大文件(无需Node.js)

解决方案:浏览器端分块流式下载并追加写入本地文件

可以不使用Node.js实现这个需求,核心是利用浏览器的FileSystem Access API——它允许网页在用户授权后直接操作本地文件,支持追加写入;同时结合流式处理响应体,避免将大文件块全部加载到内存中。

原代码的问题

你的现有代码会同时发起30个请求,把所有1GB的文件块都存在chunks数组里,等全部下载完才生成文件,这会导致浏览器内存占用飙升(最多达30GB),必然引发崩溃。正确的做法是串行下载分块,每下载一块就写入本地,写完后释放内存,再继续下一块。

实现代码

document.addEventListener("DOMContentLoaded", function () {
    const DOWNLOAD_URL = "你的30GB文件URL";
    const SLICE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB分块

    async function downloadAndAppendChunk(fileHandle, chunkIndex, totalChunks) {
        const start = chunkIndex * SLICE_SIZE;
        // 最后一块的end设为'',让服务器返回剩余所有字节
        const end = chunkIndex === totalChunks - 1 ? '' : start + SLICE_SIZE - 1;

        try {
            const response = await fetch(DOWNLOAD_URL, {
                headers: {
                    'Range': `bytes=${start}-${end}`
                }
            });

            if (!response.ok) {
                throw new Error(`下载分块 ${chunkIndex + 1} 失败,状态码:${response.status}`);
            }
            if (response.status !== 206 && chunkIndex !== totalChunks - 1) {
                throw new Error("服务器不支持Range请求,无法分块下载");
            }

            // 获取文件写入流,设置为追加模式
            const writable = await fileHandle.createWritable({ keepExistingData: true });
            // 流式读取响应体,边读边写入
            const reader = response.body.getReader();
            let done, value;

            while ({ done, value } = await reader.read()) {
                if (done) break;
                await writable.write(value);
                // 释放当前缓冲区内存
                value = null;
            }

            await writable.close();
            console.log(`分块 ${chunkIndex + 1}/${totalChunks} 下载并写入完成`);
            return true;
        } catch (error) {
            console.error(`分块 ${chunkIndex + 1} 处理失败`, error);
            return false;
        }
    }

    async function startDownload() {
        try {
            // 先请求用户授权,选择要保存的文件(会创建新文件或覆盖已有文件)
            const fileHandle = await window.showSaveFilePicker({
                suggestedName: "large-file.bin",
                types: [{
                    description: "Binary File",
                    accept: { "application/octet-stream": [".bin"] }
                }]
            });

            // 先获取文件总大小(可选,用来计算总分块数)
            const headResponse = await fetch(DOWNLOAD_URL, { method: 'HEAD' });
            const totalSize = parseInt(headResponse.headers.get('Content-Length'));
            const totalChunks = Math.ceil(totalSize / SLICE_SIZE);
            console.log(`文件总大小:${totalSize}字节,共${totalChunks}个分块`);

            // 串行下载每个分块
            for (let i = 0; i < totalChunks; i++) {
                const success = await downloadAndAppendChunk(fileHandle, i, totalChunks);
                if (!success) {
                    // 可添加重试逻辑
                    alert(`分块 ${i + 1} 下载失败,已终止`);
                    break;
                }
            }

            alert("文件下载完成!");
        } catch (error) {
            console.error("下载流程失败", error);
            alert(`下载出错:${error.message}`);
        }
    }

    document.getElementById("downloadLink1").addEventListener("click", function (event) {
        event.preventDefault();
        startDownload();
    });
});

关键细节说明

  1. FileSystem Access API授权:必须通过showSaveFilePicker让用户主动选择保存位置,这是浏览器的安全限制,无法静默写入本地文件。
  2. 流式处理:使用response.body.getReader()流式读取响应体,每次只加载一小段数据到内存,写完就释放,避免1GB块占用内存。
  3. Range请求优化:最后一块的end设为空字符串,让服务器返回剩余所有字节,无需精确计算最后一块的大小。
  4. 串行下载:用for循环+await确保完成一块再下载下一块,避免并发请求导致的内存和网络压力。

兼容性注意

  • Chrome/Edge 86+ 原生支持此API
  • Firefox 需要在about:config中开启dom.fs.enabled和dom.fs.writable_files.enabled
  • Safari 15.4+ 部分支持,可能存在兼容性问题

内容的提问来源于stack exchange,提问作者deepak vajpayee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:45:20