與其生成zip文件並從您的服務器進行傳輸,不以下載數據並將其壓縮在瀏覽器中呢?javascript
我最近從事一個副項目,該項目可根據用戶的請求生成報告。對於每一個請求,咱們的後端將生成一個報告,將其上傳到Amazon S3存儲,而後將其URL返回給客戶端。因爲生成報告須要一些時間,所以將存儲輸出文件,而且服務器將經過請求參數來緩存其URL。若是用戶訂購相同的商品,則後端將返回現有文件的URL。html
幾天前,我有一個新要求,我須要下載一個包含數百個報告的zip文件,而不是單個文件。我想到的第一個解決方案是:前端
可是此解決方案有一些缺點:java
我想出的最終解決方案是:將全部文件下載到瀏覽器中,而後將其壓縮。在這篇文章中,我將介紹如何作。git
免責聲明:在這篇文章中,我假設你已經具備有關Javascript和Promise的基本知識。若是你沒有,我建議你先了解他們,而後再回到這裏:)github
在應用新解決方案以前,個人系統容許下載一個報告文件。有不少方法能夠作到這一點,後端能夠直接經過HTTP請求響應原始文件的內容,也能夠將文件上傳到另外一個存儲設備並返回文件URL。我選擇第二種方法,由於我想緩存全部生成的文件。npm
一旦有了文件URL,在客戶端上的工做就很是簡單:在新選項卡中打開此URL。瀏覽器將完成剩下的工做如下載文件。後端
const downloadViaBrowser = url => {
window.open(url, ‘_blank’);
}
複製代碼
當下載和壓縮多個文件時,咱們不能再使用上面的簡單方法。api
解決此問題的另外一種方法是使用 fetch
來下載文件並將數據做爲Blob存儲在內存中。而後,咱們能夠將其寫入文件或將這些Blob數據合併爲zip文件。數組
const download = url => {
return fetch(url).then(resp => resp.blob());
};
複製代碼
這個函數返回一個被解析爲blob的promise。咱們能夠結合 Promise.all()
來下載多個文件。Promise.all()
將一次性完成全部的promise,若是全部的子promise都被解析,或者其中一個Promise出現錯誤,則進行解析。
const downloadMany = urls => {
return Promise.all(urls.map(url => download(url))
}
複製代碼
可是,若是咱們須要一次下載大量文件怎麼辦?假設有1000個文件?使用 Promise.all()
可能再也不是一個好主意,你的代碼將一次發送一千個請求。 這種方法有不少問題:
我考慮過的解決方案是將文件分紅多個組。假設我有1000個文件可供下載。而不是經過 Promise.all()
當即開始一次下載全部文件,我將每次下載5個文件。在完成這5個以後,我將開始另外一個包,我總共會下載250個包。
要實現這個功能,咱們能夠作一個自定義邏輯。或者我建議一個更簡單的方法,就是利用第三方庫bluebirdjs。該庫實現了許多有用的Promise函數。在這個用例中,我將使用 Promise.map()。注意這裏的Promise如今是庫提供的自定義Promise,而不是內置的Promise。
import Promise from 'bluebird';
const downloadByGroup = (urls, files_per_group=5) => {
return Promise.map(
urls,
async url => {
return await download(url);
},
{concurrency: files_per_group}
);
}
複製代碼
經過上面的實現,該函數將接收一個URL數組並開始下載全部URL,每次都具備最大 files_per_group
。該函數返回一個Promise,它將在下載全部URL時解析,並在其中任何一個失敗時拒絕。
如今我已經把全部的內容都下載到內存中了。正如我上面提到的,下載的內容被存儲爲Blob。下一步是使用這些Blob數據建立一個壓縮文件。
import JsZip from 'jszip';
import FileSaver from 'file-saver';
const exportZip = blobs => {
const zip = JsZip();
blobs.forEach((blob, i) => {
zip.file(`file-${i}.csv`, blob);
});
zip.generateAsync({type: 'blob'}).then(zipFile => {
const currentDate = new Date().getTime();
const fileName = `combined-${currentDate}.zip`;
return FileSaver.saveAs(zipFile, fileName);
});
}
複製代碼
讓咱們在這裏完成我爲此完成的全部代碼。
import Promise from 'bluebird';
import JsZip from 'jszip';
import FileSaver from 'file-saver';
const download = url => {
return fetch(url).then(resp => resp.blob());
};
const downloadByGroup = (urls, files_per_group=5) => {
return Promise.map(
urls,
async url => {
return await download(url);
},
{concurrency: files_per_group}
);
}
const exportZip = blobs => {
const zip = JsZip();
blobs.forEach((blob, i) => {
zip.file(`file-${i}.csv`, blob);
});
zip.generateAsync({type: 'blob'}).then(zipFile => {
const currentDate = new Date().getTime();
const fileName = `combined-${currentDate}.zip`;
return FileSaver.saveAs(zipFile, fileName);
});
}
const downloadAndZip = urls => {
return downloadByGroup(urls, 5).then(exportZip);
}
複製代碼
利用客戶端的功能有時對於減小後端的工做量和複雜性很是有用。
不要一次發送大量的請求。你會在前端和後端都遇到麻煩。相反,將做品分紅小塊。
介紹一些第三方庫bluebird,jszip和file-saver。他們爲我工做得很好,也可能對您有幫助:)
來源:levelup.gitconnected.com,翻譯:公衆號《前端全棧開發者》