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。