Web APP/JavaScript 下载大文件解决方案

随着浏览器处理数据能力的不断增强以及Web APP的流行,很多时候我们会需要把浏览器处理好的数据下载保存,如:将报表生成CSV、前端大数据可视化的结果保存、前端数据计算结果(在线Excel)的保存等。

关于前端生成的文件的下载,我之前也在《JavaScript生成CSV,以及中文乱码问题》中写道可以通过Data URLs来解决。但是这两天和剧中人讨论的时候突然遇到了前端生成的文件过大无法下载的问题,这里讨论一下解决方案。

1. 问题重现

我们先看例子,这个例子中我们不断地去循环扩充txt字段的内容,然后把它放在URL并模拟点击下载。

var a = document.createElement('a');
var txt = '%E5%A7%93%E5%90%8D,%E5%B9%B4%E9%BE%84%0AMofei,18,\n';
for(var i=0; i<15; i++){
  txt = txt + txt;
}
a.href='data:text/csv;charset=utf-8,\uFEFF'+txt;
a.download="text.csv";
a.click()

当i的值比较小的时候,我们可以顺利的下载文件,但是当把i调到一个比较大的数值时,就会发现这样的问题:

下载失败

经过不断地测试和推敲,我们一致认为问题可能出在了URL的长度上。

那么有没有办法绕过URL的长度限制去下载文件呢?经过一番研究我们发现了这样的一个库 FileSaver.js 虽然这个库的主要内容是一个只有5.5k的js文件,但是它的Star数量在我写这篇博客的时候已经达到了8500+,这引起了我们的好奇。

2. 解决方案

我们首先来看看它是如何实现下载大文件的功能的,通读了整个源码,我们发现这个库主要是使用了浏览器的Blob以及URL.createObjectURL()方法,那么这两个平时很少接触到的东东到底是个什么呢?

2.1 Blob是什么?

在MDN的Blob的介绍中我们找到了这样的文字介绍:

A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system.

简单说,Blob就是一种类文件的对象,用来存储非JavaScript基础类型的数据。再简单说,就是用来存储一些JS不可描述的数据,比如通过File API读到的一些稀奇古怪的视频资源、二进制资源、应用程序等。

他的构造方法比较简单:

var aBlob = new Blob( array[, options]);

如果我们想要存储一个html的文件,就可以写成:

var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; 
var oMyBlob = new Blob(aFileParts, {type : 'text/html'});

第一个参数是用Array包裹的Html的文本,第二个参数指明了这个文件的类型是html(text/html)。

上面也提到了Blob通常是和File API一起使用的,因此浏览器允许的Blob大小自然就会比Url的长度大多了。

说到这里,大家一定会自然的想到可以用Blob存储大的文件,但是如果把Blob再转成URL,岂不是又会很长并超限了么?这时候就该createObjectURL闪亮登场了。

2.2 URL.createObjectURL() 是什么?

createObjectURL方法源于(window.)URL接口,URL接口主要用来创建Object URLs的,简单来说,就是可以把一个对象转换成URL。 目前createObjectURL主要用转换File对象或者Blob对象。他的语法也比较简单:

var objectURL = URL.createObjectURL(object);

好了,接下啦我们就可以干大事了!!

2.3 双剑合璧

有了Blob和OjbectURL之后,我们就可以把他们结合在一起来实现更大的文件下载了。直接看我们修改后的内容:

var a = document.createElement('a');
var txt = '%E5%A7%93%E5%90%8D,%E5%B9%B4%E9%BE%84%0AMofei,18,\n';
for(var i=0;i<20;i++){
  txt = txt + txt;
}
var t = new Blob([txt], {type : 'application/csv'});
a.href=URL.createObjectURL(t)
a.download="Mofei的CSV.csv";
a.click();

我们用new Blob包装了我们的txt,然后通过createObjectURL去把Blob转换成对象地址,这样浏览器就可以下载很大的文件了。

3. 更多

FileSaver.js的作者主要是通过Blob以及ObjectURL的方法实现了浏览器下载大文件的可能性,但是实际上这个方案的可用大小往往取决于浏览器对blob的限制或者用户内存的限制(对于大多数用户来说500M的Blob大小已经主够前端去使用了)。作者在项目中还提及到了另外一个神奇且无敌的库 StreamSaver.js 这个库使用service worker实现了更大的前端下载空间,有兴趣的小伙伴也可以去了解一下。

Write a response...
Mofei Zhu
publish