Javascript 中的大型 blob 文件

10

我有一个XHR对象,可以下载1GB的文件。

function getFile(callback)
{
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
        if (xhr.status == 200) {
            callback.apply(xhr);
        }else{
            console.log("Request error: " + xhr.statusText);
        }
    };

    xhr.open('GET', 'download', true);
    xhr.onprogress = updateProgress;
    xhr.responseType = "arraybuffer";
    xhr.send();
}

但是,即使使用 worker,File API 也无法将所有内容加载到内存中,否则会出现内存不足的情况...

btn.addEventListener('click', function() {
    getFile(function() {
        var worker = new Worker("js/saving.worker.js");
        worker.onmessage = function(e) {
            saveAs(e.data); // FileSaver.js it creates URL from blob... but its too large
        };

        worker.postMessage(this.response);
    });
});

Web Worker

onmessage = function (e) {
    var view  = new DataView(e.data, 0);
    var file = new File([view], 'file.zip', {type: "application/zip"});
    postMessage('file');
};

我不打算压缩文件,这个文件已经从服务器压缩过了。

我原本想将它存储在indexedDB中,但是无论如何我都需要加载blob或文件,即使我通过范围字节请求,迟早我也会构建这个巨大的blob...

我想创建blob:URL并在浏览器下载后将其发送给用户

我将使用Google Chrome的FileSystem API,但我希望能为Firefox设计一个类似的东西,我研究了File Handle Api但是没有找到...

我必须为Firefox构建扩展程序,才能像FileSystem针对Google Chrome那样做吗?


Ubuntu 32位


当你下载一个较小的*.zip文件时,它是否有效?这个错误对我来说很熟悉,“内存不足”。 - user5066707
控制台只报了一个内存不足的错误。 - Gabriel dos Anjos
1
我想知道如果在btn'click'事件中执行它们,你会得到多少个警报。 - user5066707
2
这里的目标是下载一个1GB的大文件 - 但是你在内存中使用1GB文件做什么呢?处理这个文件的唯一方法是流式传输。使用XHR和内存中拥有这个1GB文件的意图是什么?需要更多的明确性。具体来说,这个文件是什么,你想用它做什么? - AntonB
1
// FileSaver.js会从blob创建URL...但是它太大了,所以它只能工作到那个点? - the8472
显示剩余12条评论
3个回答

8

使用ajax加载1GB以上的内容不仅不方便监视下载进度,还会占用大量内存。

相反,我会发送一个带有Content-Disposition头的文件来保存文件。


然而,有一些方法可以绕过它以监视进度。第一种选择是拥有第二个websocket,用于在您正常进行get请求下载时发出已下载多少的信号。另一种选项将在底部描述。


我知道您在对话中谈到了使用Blink沙盒文件系统,但它有一些缺点。如果使用持久存储,则可能需要权限。它只允许剩余可用磁盘的20%。如果Chrome需要释放一些空间,那么它将丢弃最近用于最新文件的任何其他域的临时存储。此外,它在私人模式下无法工作。
更不用说它一直在减少对其的支持,可能永远不会出现在其他浏览器中-但他们很可能不会删除它,因为许多站点仍然依赖它


处理此大型文件的唯一方法是使用流。这就是为什么我创建了一个StreamSaver。目前它只能在Blink(Chrome和Opera)中使用,但它最终将得到其他浏览器的支持,并备有whatwg规范作为标准。

fetch(url).then(res => {
    // One idea is to get the filename from Content-Disposition header...
    const size = ~~res.headers.get('Content-Length')
    const fileStream = streamSaver.createWriteStream('filename.zip', size)
    const writeStream = fileStream.getWriter()
    // Later you will be able to just simply do
    // res.body.pipeTo(fileStream)
    // instead of pumping

    const reader = res.body.getReader()
    const pump = () => reader.read()
        .then(({ value, done }) => {
            // here you know how large the value (chunk) is and you can
            // figure out the download speed/progress when comparing it to the size

            return done 
                ? writeStream.close()
                : writeStream.write(value).then(pump)
        )

    // Start the reader
    pump().then(() =>
        console.log('Closed the stream, Done writing')
    )
})

这不会占用任何内存


我能为Firefox创建一个做更多或更少相同事情的扩展吗?有什么解决方法吗? - Gabriel dos Anjos
几乎所有的事情都可以用Firefox扩展实现,所以我想是可以的。但我不认为人们会安装它 :-/ 你可以通过帮助Firefox实现流来做出贡献 :-) - Endless

7
我有一个理论,即将文件分成块并将它们存储在indexedDB中,然后稍后将它们合并在一起就可以工作。
Blob不是由数据组成的...它更像是指向可以从中读取文件的位置的指针enter image description here
意味着,如果你将它们存储在indexedDB中,然后做一些像这样的事情(使用FileSaver或替代方案)。
finalBlob = new Blob([blob_A_fromDB, blob_B_fromDB])
saveAs(finalBlob, 'filename.zip')

但是我不能确定这个,因为我还没有测试过,如果有其他人测试就好了。

1
我已经开始测试它了。并且我认为它有效 :) 帮我审核一下这个问题: https://github.com/jimmywarting/StreamSaver.js/pull/18 - Endless
我也想尝试,但是我没有时间 :/ 开发进度太晚了 - Gabriel dos Anjos
从一个大小为450MB的文件中,它进行得很顺利。 - Gabriel dos Anjos
finalBlob = new Blob([blob_A_fromDB, blob_B_fromDB]) - 这行代码将会在内存中创建一个可能非常巨大的 Blob 对象... 在内存较小(RAM)的弱电脑上,这行代码会导致浏览器崩溃。 - freethinker
@OlegYudovich 不是必要的。就像我说的,blob可以是指向磁盘上文件位置的指针。试试这个:创建一个或多个文件输入(可能有多个文件选择)。从您的驱动器中选择几个大型(+GB)文件并将它们合并在一起,看看它是否会使浏览器崩溃以及速度快/慢如何。最终的blob只是一个包含信息的容器,您可以从中读取chunkA + chunkB。 - Endless

0

Blob很酷,直到你想下载一个大文件,因为它将所有内容存储在内存中,所以blob有一个600MB的限制(chrome)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接