zachguo
zachguo

Reputation: 6716

How can I download a file using window.fetch?

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

Answers (12)

Mariusz Pawelski
Mariusz Pawelski

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

zachguo
zachguo

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

noraj
noraj

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

frank-dspeed
frank-dspeed

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

mustafa candan
mustafa candan

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

Lucas Matos
Lucas Matos

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

lemonskunnk
lemonskunnk

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

Yuci
Yuci

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:

  • You can dynamically generate the link element if necessary.
  • This approach is especially useful for downloading, via a stream, big-sized files that are dynamically generated on the server side

Upvotes: 6

CDM
CDM

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

Daniel
Daniel

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

Zibri
Zibri

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

Michael Hobbs
Michael Hobbs

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

Related Questions