如何从获取请求中的 readableStream 响应中获取可下载文件
How to get a downloadable file from a readableStream response in a fetch request
我有一个 React 应用程序向我的 rails API 发送 POST 请求。我希望我的 API 端点生成一个 csv 文件,然后将该文件发送回 React 应用程序。我希望浏览器为最终用户下载 csv 文件。
端点如下所示:
def generate
// next line builds the csv in tmp directory
period_recap_csv = period_recap.build_csv
// next line is supposed to send back the csv as response
send_file Rails.root.join(period_recap.filepath), filename: period_recap.filename, type: 'text/csv'
end
在前端,我的请求是这样的:
export function generateCsvRequest(startDate, endDate) {
fetch("http://localhost:3000/billing/finance-recaps/generate", {
method: "post",
headers: {
Authorisation: `Token token=${authToken}`,
'Accept': 'text/csv',
'Content-Type': 'application/json',
'X-Key-Inflection': 'camel',
},
//make sure to serialize your JSON body
body: JSON.stringify({
start_date: startDate,
end_date: endDate
})
})
.then( (response) => {
console.log('resp', response);
return response;
}).then((data) => {
// what should I do Here with the ReadableStream I get back ??
console.log(data);
}).catch(err => console.error(err));
}
作为响应主体,我得到一个 readableStream :
我现在应该如何处理该 ReadableStream 对象以在最终用户浏览器上启动该 CSV 文件的下载?
如果结束点是流,一种解决方案是检索整个文件然后保存它。
Disclaimer: the solution I offer below involve loading a whole pdf file in memory and avoid opening a new window tab to download the file (using downloadjs: very useful to rename and save the downloaded data). If anyone get a solution to avoid loading the whole file in memory before saving it, fell free to share :) :).
代码如下:
import download from 'downloadjs'
fetch(...)
.then(response => {
//buffer to fill with all data from server
let pdfContentBuffer = new Int8Array();
// response.body is a readableStream
const reader = response.body.getReader();
//function to retreive the next chunk from the stream
function handleChunk({ done, chunk }) {
if (done) {
//everything has been loaded, call `download()` to save gthe file as pdf and name it "my-file.pdf"
download(pdfContentBuffer, `my-file.pdf`, 'application/pdf')
return;
}
// concat already loaded data with the loaded chunk
pdfContentBuffer = Int8Array.from([...pdfContentBuffer, ...chunk]);
// retreive next chunk
reader.read().then(handleChunk);
}
//retreive first chunk
reader.read().then(handleChunk)
})
.catch(err => console.error(err))
我分享它希望这对其他人有帮助。
当您使用提取 API 时,您的响应对象有很多方法来处理您获得的数据。最常用的是json()
。如果您需要从服务器下载文件,您需要的是 blob()
,其工作方式与 json()
.
相同
response.blob().then(blob => download(blob))
有很多npm包可以下载文件。 file-saver 就是其中之一。不过,一种无需依赖项即可工作的方法是使用 a
标记。类似的东西:
function download(blob, filename) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
无论如何,使用依赖项会涵盖更多边缘情况,而且通常更兼容。希望有用
如果您想在另一个选项卡中显示 pdf 而不是下载它,您可以使用 window.open
并传递由 window.URL.createObjectURL
生成的 URL。
function showInOtherTab(blob) {
const url = window.URL.createObjectURL(blob);
window.open(url);
}
我有一个 React 应用程序向我的 rails API 发送 POST 请求。我希望我的 API 端点生成一个 csv 文件,然后将该文件发送回 React 应用程序。我希望浏览器为最终用户下载 csv 文件。
端点如下所示:
def generate
// next line builds the csv in tmp directory
period_recap_csv = period_recap.build_csv
// next line is supposed to send back the csv as response
send_file Rails.root.join(period_recap.filepath), filename: period_recap.filename, type: 'text/csv'
end
在前端,我的请求是这样的:
export function generateCsvRequest(startDate, endDate) {
fetch("http://localhost:3000/billing/finance-recaps/generate", {
method: "post",
headers: {
Authorisation: `Token token=${authToken}`,
'Accept': 'text/csv',
'Content-Type': 'application/json',
'X-Key-Inflection': 'camel',
},
//make sure to serialize your JSON body
body: JSON.stringify({
start_date: startDate,
end_date: endDate
})
})
.then( (response) => {
console.log('resp', response);
return response;
}).then((data) => {
// what should I do Here with the ReadableStream I get back ??
console.log(data);
}).catch(err => console.error(err));
}
作为响应主体,我得到一个 readableStream :
我现在应该如何处理该 ReadableStream 对象以在最终用户浏览器上启动该 CSV 文件的下载?
如果结束点是流,一种解决方案是检索整个文件然后保存它。
Disclaimer: the solution I offer below involve loading a whole pdf file in memory and avoid opening a new window tab to download the file (using downloadjs: very useful to rename and save the downloaded data). If anyone get a solution to avoid loading the whole file in memory before saving it, fell free to share :) :).
代码如下:
import download from 'downloadjs'
fetch(...)
.then(response => {
//buffer to fill with all data from server
let pdfContentBuffer = new Int8Array();
// response.body is a readableStream
const reader = response.body.getReader();
//function to retreive the next chunk from the stream
function handleChunk({ done, chunk }) {
if (done) {
//everything has been loaded, call `download()` to save gthe file as pdf and name it "my-file.pdf"
download(pdfContentBuffer, `my-file.pdf`, 'application/pdf')
return;
}
// concat already loaded data with the loaded chunk
pdfContentBuffer = Int8Array.from([...pdfContentBuffer, ...chunk]);
// retreive next chunk
reader.read().then(handleChunk);
}
//retreive first chunk
reader.read().then(handleChunk)
})
.catch(err => console.error(err))
我分享它希望这对其他人有帮助。
当您使用提取 API 时,您的响应对象有很多方法来处理您获得的数据。最常用的是json()
。如果您需要从服务器下载文件,您需要的是 blob()
,其工作方式与 json()
.
response.blob().then(blob => download(blob))
有很多npm包可以下载文件。 file-saver 就是其中之一。不过,一种无需依赖项即可工作的方法是使用 a
标记。类似的东西:
function download(blob, filename) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
无论如何,使用依赖项会涵盖更多边缘情况,而且通常更兼容。希望有用
如果您想在另一个选项卡中显示 pdf 而不是下载它,您可以使用 window.open
并传递由 window.URL.createObjectURL
生成的 URL。
function showInOtherTab(blob) {
const url = window.URL.createObjectURL(blob);
window.open(url);
}