Reputation: 6716
If I want to download a file, what should I do in the then
block below?
function downloadFile(token, fileId) {
let url = `https://www.googleapis.com/drive/v2/files/${fileId}?alt=media`;
return fetch(url, {
method: 'GET',
headers: {
'Authorization': token
}
}).then(...);
}
Note: The code is on the client-side.
Upvotes: 195
Views: 292674
Reputation: 28832
EDIT: syg answer is better. Just use downloadjs library.
The answer I provided works well on Chrome, but on Firefox and IE you need some different variant of this code. It's better to use this library for that.
I had similar problem (need to pass authorization header to download a file so this solution didn't help).
But based on this answer, you can use createObjectURL
to make your browser save a file downloaded by Fetch API.
getAuthToken()
.then(token => {
fetch("http://example.com/ExportExcel", {
method: 'GET',
headers: new Headers({
"Authorization": "Bearer " + token
})
})
.then(response => response.blob())
.then(blob => {
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = "filename.xlsx";
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
a.click();
a.remove(); //afterwards we remove the element again
});
});
Upvotes: 147
Reputation: 6716
I temporarily solve this problem by using download.js and blob
.
let download = require('./download.min');
...
function downloadFile(token, fileId) {
let url = `https://www.googleapis.com/drive/v2/files/${fileId}?alt=media`;
return fetch(url, {
method: 'GET',
headers: {
'Authorization': token
}
}).then(function(resp) {
return resp.blob();
}).then(function(blob) {
download(blob);
});
}
It's working for small files, but maybe not working for large files. I think I should dig Stream more.
Upvotes: 68
Reputation: 4622
This is Lucas Matos answer (no libraries only fetch API) but with support for a custom name.
const url ='http://sample.example.file.doc'
const authHeader ="Bearer 6Q************"
const options = {
headers: {
Authorization: authHeader
}
};
fetch(url, options)
.then( res => res.blob() )
.then( blob => {
var fileURL = URL.createObjectURL(blob);
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.download = `whatever.ext`;
fileLink.click();
});
This solution does not allow you to change filename for the downloaded file. The filename will be a random uuid.
Upvotes: 2
Reputation: 1112
I guess the correct today answer is
fetch(window.location).then(async res=>res.body.pipeTo(await (await showSaveFilePicker({
suggestedName: 'Any-suggestedName.txt'
})).createWritable()));
Upvotes: 2
Reputation: 836
No libraries only fetch API. Also you can change the file name
function myFetch(textParam, typeParam) {
fetch("http://localhost:8000/api", {
method: "POST",
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
},
body: JSON.stringify({
text: textParam,
barcode_type_selection: typeParam,
}),
})
.then((response) => {
return response.blob();
})
.then((blob) => {
downloadFile(blob);
});
}
Here is the download file function
function downloadFile(blob, name = "file.pdf") {
const href = URL.createObjectURL(blob);
const a = Object.assign(document.createElement("a"), {
href,
style: "display:none",
download: name,
});
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(href);
a.remove();
}
Upvotes: 8
Reputation: 2918
This is more shorter and efficient, no libraries only fetch API
const url ='http://sample.example.file.doc'
const authHeader ="Bearer 6Q************"
const options = {
headers: {
Authorization: authHeader
}
};
fetch(url, options)
.then( res => res.blob() )
.then( blob => {
var file = window.URL.createObjectURL(blob);
window.location.assign(file);
});
This solution does not allow you to change filename for the downloaded file. The filename will be a random uuid.
Upvotes: 181
Reputation: 376
A similar but cleaner and more reliable solution IMO.
On your fetch function...
fetch(...)
.then(res =>
{
//you may want to add some validation here
downloadFile(res);
}
)
and the downloadFile function is...
async function downloadFile(fetchResult) {
var filename = fetchResult.headers.get('content-disposition').split('filename=')[1];
var data = await fetchResult.blob();
// It is necessary to create a new blob object with mime-type explicitly set
// otherwise only Chrome works like it should
const blob = new Blob([data], { type: data.type || 'application/octet-stream' });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE doesn't allow using a blob object directly as link href.
// Workaround for "HTML7007: One or more blob URLs were
// revoked by closing the blob for which they were created.
// These URLs will no longer resolve as the data backing
// the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
return;
}
// Other browsers
// Create a link pointing to the ObjectURL containing the blob
const blobURL = window.URL.createObjectURL(blob);
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = blobURL;
tempLink.setAttribute('download', filename);
// Safari thinks _blank anchor are pop ups. We only want to set _blank
// target if the browser does not support the HTML5 download attribute.
// This allows you to download files in desktop safari if pop up blocking
// is enabled.
if (typeof tempLink.download === 'undefined') {
tempLink.setAttribute('target', '_blank');
}
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
setTimeout(() => {
// For Firefox it is necessary to delay revoking the ObjectURL
window.URL.revokeObjectURL(blobURL);
}, 100);
}
(downloadFile function source: https://gist.github.com/davalapar/d0a5ba7cce4bc599f54800da22926da2)
Upvotes: 5
Reputation: 30089
As per some of the other answers, you can definitely use window.fetch and download.js to download a file. However, using window.fetch with blob has the restriction on memory imposed by the browser, and the download.js also has its compatibility restrictions.
If you need to download a big-sized file, you don't want to put it in the memory of the client side to stress the browser, right? Instead, you probably prefer to download it via a stream. In such a case, using an HTML link to download a file is one of the best/simplest ways, especially for downloading big-sized files via a stream.
Step One: create and style a link element
You can make the link invisible but still actionable.
HTML:
<a href="#" class="download-link" download>Download</a>
CSS:
.download-link {
position: absolute;
top: -9999px;
left: -9999px;
opacity: 0;
}
Step Two: Set the href
of the link, and trigger the click
event
JavaScript
let url = `https://www.googleapis.com/drive/v2/files/${fileId}?alt=media`;
const downloadLink = document.querySelector('.download-link')
downloadLink.href = url + '&ts=' + new Date().getTime() // Prevent cache
downloadLink.click()
Notes:
Upvotes: 6
Reputation: 517
I tried window.fetch but that ended up being complicated with my REACT app
now i just change window.location.href and add query params like the jsonwebtoken
and other stuff
.
///==== client side code =====
var url = new URL(`http://${process.env.REACT_APP_URL}/api/mix-sheets/list`);
url.searchParams.append("interval",data.interval);
url.searchParams.append("jwt",token)
window.location.href=url;
// ===== server side code =====
// on the server i set the content disposition to a file
var list = encodeToCsv(dataToEncode);
res.set({"Content-Disposition":`attachment; filename=\"FileName.csv\"`});
res.status(200).send(list)
the end results actually end up being pretty nice, the window makes request and downloads the file and doesn't event switch move the page away, its as if the window.location.href
call was like a lowkey fetch()
call.
Upvotes: 2
Reputation: 363
Using dowloadjs. This will parse the filename from the header.
fetch("yourURL", {
method: "POST",
body: JSON.stringify(search),
headers: {
"Content-Type": "application/json; charset=utf-8"
}
})
.then(response => {
if (response.status === 200) {
filename = response.headers.get("content-disposition");
filename = filename.match(/(?<=")(?:\\.|[^"\\])*(?=")/)[0];
return response.blob();
} else {
return;
}
})
.then(body => {
download(body, filename, "application/octet-stream");
});
};
Upvotes: 9
Reputation: 9827
function download(dataurl, filename) {
var a = document.createElement("a");
a.href = dataurl;
a.setAttribute("download", filename);
a.click();
return false;
}
download("data:text/html,HelloWorld!", "helloWorld.txt");
or:
function download(url, filename) {
fetch(url).then(function(t) {
return t.blob().then((b)=>{
var a = document.createElement("a");
a.href = URL.createObjectURL(b);
a.setAttribute("download", filename);
a.click();
}
);
});
}
download("https://get.geojs.io/v1/ip/geo.json","geoip.json")
download("data:text/html,HelloWorld!", "helloWorld.txt");
Upvotes: 25
Reputation: 1693
Here is an example using node-fetch for anyone that finds this.
reportRunner({url, params = {}}) {
let urlWithParams = `${url}?`
Object.keys(params).forEach((key) => urlWithParams += `&${key}=${params[key]}`)
return fetch(urlWithParams)
.then(async res => ({
filename: res.headers.get('content-disposition').split('filename=')[1],
blob: await res.blob()
}))
.catch(this.handleError)
}
Upvotes: 8