How can I download a file using window.fetch?

153,149

Solution 1

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.

Solution 2

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 library for that.


I had similar problem (need to pass authorization header to download a file so this solution didn't helped).

But based on this answer you can use createObjectURL to make 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         
        });
    });

Solution 3

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.

Solution 4

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");

Solution 5

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");
    });
};
Share:
153,149
zachguo
Author by

zachguo

Updated on July 02, 2021

Comments

  • zachguo
    zachguo almost 3 years

    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 codes are in client-side.

  • tarikakyol
    tarikakyol over 7 years
    just bear in mind the current blob size limit is around 500mb for browsers
  • messerbill
    messerbill over 5 years
    @David I updated the answer. Should now also work in FF. The problem was, that FF wants the link element to be appended to the DOM. This is not mandatory in Chrome or Safari.
  • FabricioG
    FabricioG about 5 years
    Where does this save it to? @Michael Hobbs
  • Michael Hobbs
    Michael Hobbs about 5 years
    The function returns an object {filename, blob}, you can use fs.writefile to save the file to disk.
  • Phate
    Phate about 5 years
    Nice, just one thing: would it be possible to get the file name from the server response so to let the user download it with its real name?
  • Andrew Simontsev
    Andrew Simontsev about 5 years
    It makes sense to call URL.revokeObjectURL in the end to avoid a memory leak.
  • thargenediad
    thargenediad almost 5 years
    This mostly worked, but I ended up using the regex from this other answer instead. So... fileName = fileName.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1] ? fileName.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1] : fileName;
  • jbarradas
    jbarradas almost 5 years
    @AndrewSimontsev Great tip, thanks for the input! I edited the response, let me know if it is correct that way. Tested on my code and seems ok!
  • aizquier
    aizquier over 4 years
    Firefox does not support filename.match(), I replaced that part with: filename = response.headers.get("content-disposition").split(";")[1].sp‌​lit('"')[1]; . Besides, the server must declare the header Access-Control-Expose-Headers: Content-Disposition in order to allow the browser to read the content-disposition header.
  • Anton
    Anton over 4 years
    is there any way to set the file name?
  • Lucas Matos
    Lucas Matos over 4 years
    Yes, you can add Content-Disposition to the Headers obj, here is the documentation developer.mozilla.org/en-US/docs/Web/HTTP/Headers/…
  • miran80
    miran80 almost 4 years
    @LucasMatos I added “Content-Disposition” header to options object and I do get the correct header for my file when inspecting it in network tab, but then the blob is created and the name is thrown away so I end up with a generated random name. Do you know how to pass the name to blob with your solution?
  • Dejan
    Dejan almost 4 years
  • aggregate1166877
    aggregate1166877 over 3 years
    I disagree that using a library is a better answer :). Using external libraries is not always an option, and when it is, then finding libraries is easy. It's not answer worthy, which is likely why your answer has more votes than the accepted answer despite the accepted being 2 years older.
  • JeffR
    JeffR over 3 years
    how could you make the CSS above Inline in the HTML?
  • Yuci
    Yuci over 3 years
    @JeffR, it would like something like this: <a href="#" class="download-link" style="position: absolute; top: -9999px; left: -9999px; opacity: 0" download>Download</a>, and the class attribute here is only for querySelector to use in the JS code.
  • JeffR
    JeffR over 3 years
    When I execute the line 'downloadLink.click()', the rest of my HTML code disappears. Any reason for that? If I comment the 'downloadLink.click()' out and instead show the Download link, all html works fine. Any ideas?
  • Yuci
    Yuci over 3 years
    @JeffR Maybe something to do with the download attribute of the a HTML element. This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. This attribute only works for same-origin URLs. Also try without the '&ts=' + new Date().getTime() part, to see if it makes any difference to your case.
  • Tobias Ingebrigt Ørstad
    Tobias Ingebrigt Ørstad about 3 years
    This only opens the file in the same tab for me (using Chrome), does not download the file.
  • Lucas Matos
    Lucas Matos about 3 years
    Hello @TobiasIngebrigtØrstad can you share your code to review deeper the issue?
  • noszone
    noszone almost 3 years
    Hello Lucas, your code working good, downloading a file with random name. I've tried to add headers, but no luck at all. stackoverflow.com/questions/67767954/… . Could you please head up to this?
  • Lucas Matos
    Lucas Matos almost 3 years
    @noszone Here is the documentation, about how to capture headers on the fetch response developer.mozilla.org/en-US/docs/Web/API/Response/headers
  • luke_16
    luke_16 almost 3 years
    @LucasMatos I tried editing your answer but the queue is full. In order to assert a name to the file being downloaded is to add an extra line: var file = new File([blob], "filename.extension"); file = window.URL.createObjectURL(file);
  • Mmm
    Mmm almost 3 years
    This is a helpful solution, thanks! As written the filename value comes through twice in filename. Changing the first line of downloadFile to var filename = fetchResult.headers.get('content-disposition').split('filena‌​me=')[1].split(';')[‌​0].slice(1, -1); to strip off the second part (UTF/percent encoded) and the leading and trailing quotation marks works perfectly for me. (Assuming no semicolon in the filename!)
  • Mmm
    Mmm over 2 years
    This mostly works. Except Apple is behind times and your regex fails on Safari. :( aizquier's answer works.
  • Joey Carlisle
    Joey Carlisle over 2 years
    Also looking at the code on downloadjs, they use the same method of a temp <a/> to save the file. So the library doesn't necessarily do it a better way.
  • YakovL
    YakovL over 2 years
    @Phate to do so, you should pass an object from server, which contains not only the data but also the name. Once you do that, you can deal with its field separately, but for that you should see other answers here as (as far as I understand) .blob method is specific one of object which fetch resolves with
  • YakovL
    YakovL over 2 years
    @luke_16 your suggestion didn't work for me: what worked but without setting filename is const fileUrl = window.URL.createObjectURL(blob); window.location.assign(fileUrl), but when I modified it to const file = new File([blob], fileName); const fileUrl = window.URL.createObjectURL(file); window.location.assign(fileUrl), I get browser trying to display the binary on a different url (like blob:blob://http://localhost:8070/7174db61-b974-44fb-8a15-94‌​6f400378d0). Any ideas what's wrong with it?
  • evolutionxbox
    evolutionxbox over 2 years
    This code seems to change the URL of the page?
  • Timo
    Timo about 2 years
    You need a node plugin that can resolve require -see first line - on the client side, such as bundler.js and make the download package available.
  • Ricky Levi
    Ricky Levi about 2 years
    not sure why, but i'm not getting the same binary as going directly to the URL ( i'm testing on an excel file ) direct URL in the browser -> open w/o a warning in Excel, and via fetch - raise a warning that it might be corrupted (?) ( clicking "Yes" to load it anyway - is loading it OK with all the data ... )