Axios处理文件下载时,需要配置responseType
将返回数据处理成指定格式,官方文档是这样写的:
{
// `responseType` indicates the type of data that the server will respond with
// options are: 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser only: 'blob'
responseType: 'json', // default
}
其中设置arraybuffer
、blob
两个值都可以对文件进行处理,stream
没有效果。
之前在测试朋友的大文件传输代码时发现个现象:arraybuffer
时浏览器内存会不断占用,如果数据引用不释放内存是不会被释放的。而blob
也会占用内存,但到一定层度即使引用没释放但内存也会释放,仔细对比一看磁盘读写会不断升高,而且还这产生一些卡顿。是不是有点奇怪?
如果继续想的话还能有些疑问,arraybuffer
、blob
都可以处理文件?有什么区别?为什么设置blob
会释放内存?明明写的可以设置stream
但怎么又没效果?文档还说blob仅在浏览器下支持,Node下不支持?带着疑问后续又查了一些资料。
ArrayBuffer与Blob
看定义的话,先翻翻MDN:
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
从定义可以知晓,两者都是对二进制数据进行操作。MDN描述的还比较模糊,《现代 JavaScript 教程》中写的比较清楚:“基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用”。Blob支持的类型更加复合,既然也是操作二进制数据所以核心也是基于ArrayBuffer,但更主要是对文件进行操作。所以两者大部分情况下能够替换使用也就很容易理解了。但还不够解释以上其他问题。
Blob
继续查找资料,翻到Chrome设计文档Chrome's Blob Storage System Design中有对Blob详细描述。
之前的疑问在这里就有答案了:
If the in-memory space for blobs is getting full, or a new blob is too large to be in-memory, then the blob system uses the disk. This can either be paging old blobs to disk, or saving the new too-large blob straight to disk.
大意是说,当blob的内存空间占满时,或者新创建的blob太大,剩余的内存空间放不下了,blob会转存到磁盘中。可以是转存旧的blob数据,也可以是将新的blob直接存储到磁盘。
同时还提到了,在使用blob时应该避免快速创建非常多的blob,特别是数据量非常大的,这会导致浏览器要将blob写入到磁盘后才能渲染器才能继续处理后续数据。这样也就解释了为什么之前blob有出现卡顿的情况。
总结一下差异:
ArrayBuffer:仅对内存操作,是最基础的二进制对象。所有的数据都放在内存中,当有大量的ArrayBuffer时等于数据全在内存中,就容易导致浏览器标签页因内存超过限制而崩溃。
Blob:blob的数据存储比较复合,所引用的数据不仅仅在内存中,也可能存在磁盘上。当数据超过一定量时会将数据从内存转存到磁盘中。这也符合blob的名称二进制大数据对象(Binary Large Object),对大文件对象有做专门的优化。
综合看来,如果Axios处理文件数据,还是配置blob比较适合。
另一个问题,Axios中为什么说blob仅浏览器可用?这个比较容易找到答案,贺师俊在知乎有个回答:
注意,Blob并不像ArrayBuffer是JS语言内置的,而是Web API,Node.js的API里就没有Blob。这也是为什么MDN说「Blobs can represent data that isn't necessarily in a JavaScript-native format」(中文版的翻译「Blob表示的不一定是JavaScript原生格式的数据」反而比英文原文难理解)。
不看这说明是真不理解MDN的那段描述,在《现代 JavaScript 教程》中其实也有提到,但只在Blob章节开头提了ArrayBuffer是ECMA 标准的一部分,没提说Blob是不是,看着也是会觉得有些奇怪。
不过这个回答是2020年的,当时Node还不支持Blob,到Node18版本发布已经正式支持Blob类型了,详细的可以看Node官方文档class-blob中History表,所以现在Node中也是支持Blob了。
Stream
最后是Stream,先说说Stream模式与Arraybuffer(Node中对应的是Buffer)模式应用的差异。
在大文件读取的场景下,使用Arraybuffer会将所有数据全部写入内存后再处理,文件很大时很可能导致内存爆了。如果使用Stream数据依然是存入内存,但存入的数据会立即就开始处理,不必等到所有数据加载完再开始,这样只需要消耗极小的内存就能完成对文件的处理。
Node中是有Stream模式相关的API,那浏览器呢?也是有的,Chrome从59版本开始其实是有Stream API的,网络请求需要配合fetch使用。
翻阅代码,可以发现Axios浏览器请求还是基于XMLHttpRequest的,axios/lib/adapters/xhr.js
源码中responseType数据没有处理直接传入XMLHttpRequest对象的。
那么XMLHttpRequest的responseType是否支持设置为stream?来看看WHATWG对XMLHttpRequest支持的类型描述:
enum XMLHttpRequestResponseType {
"",
"arraybuffer",
"blob",
"document",
"json",
"text"
};
可知,XMLHttpRequest是不支持的。咦?很奇怪,axiox文档怎么写的是支持?
翻了一圈issue,发现有提到axios准备增加一个新的adapter(使用的是fetch)来支持stream。回头又找了一圈代码,没发现有新增的模块。继续翻翻issue和discussions,之前的相关信息都已经关闭了,但在Axios next的关联中有一个相关issue还是打开的,相关PR也还未合并,查看代码版本目前处于beta5,也有半年没更新了。
也难怪主版本中没看到过相关代码,目前看来相关改动还没有确定下来。实际测试中stream也没支持成功,流数据返回的话会解析成字符串。如果非常想在axios中接收stream数据,可以尝试使用还在测试中的模块,将adapter配置更换一下。
总之目前为止,如果想使用stream传输数据还是转向用fetch吧。
补充:2024年5月19日 axios 发布了 1.7.0 正式版,添加了 fetch 适配器,直接升级新版就能支持了。
总结
总结一下:
blob:的名称是二进制大数据对象,对大型的二进制数据有优化处理,能够减少内存的压力。同时blob不是JavaScript内置的标准,Node在早期版本不支持,但现在都已经支持了。axios在传输文件数据时候还是推荐配置为blob,避免内存占用过大而崩溃。
arraybuffer:是原始的二进制数据,所有数据都会放在内存中。如果积累过多的arraybuffer不释放,导致内存占用过多导致页面崩溃,使用的时候需要看实际情况选择。
stream:浏览器已经有相关API可用,但在网络请求中,基于XMLHttpRequest无法支持stream,fetch中才支持stream。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。