如何使用 window.fetch 下载文件?

Posted

技术标签:

【中文标题】如何使用 window.fetch 下载文件?【英文标题】:How can I download a file using window.fetch? 【发布时间】:2015-12-09 07:45:31 【问题描述】:

如果我要下载文件,我应该在下面的then 块中做什么?

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(...);

注意代码在客户端。

【问题讨论】:

如何将下载属性添加到 URL 为 https://www.googleapis.com/drive/v2/files/$fileId?alt=media 的链接 @Arjun 如何将令牌添加到请求的标头中? @anhtv13 对不起,我不明白你的问题。根据我最初的评论,我建议的解决方案是创建一个具有download 属性的<a> 元素并模拟对该元素的点击,所有这些都使用javascript。请参阅Zibri's answer(顺便说一句,这是在我发表评论之后发布的)。 这个api是安全的,不能直接去url下载,需要给header传一个token。类似"'Authorization":"Bearer" + 。在这种情况下,如何将令牌添加到标头? 【参考方案1】:

一种类似但更清洁、更可靠的 IMO 解决方案。

关于你的 fetch 函数...

fetch(...)    
.then(res => 
    
        //you may want to add some validation here
        downloadFile(res);
    
)

而downloadFile函数是……

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函数来源:https://gist.github.com/davalapar/d0a5ba7cce4bc599f54800da22926da2)

【讨论】:

这是一个有用的解决方案,谢谢!正如所写,文件名值在filename 中出现两次。将downloadFile 的第一行更改为var filename = fetchResult.headers.get('content-disposition').split('filename=')[1].split(';')[0].slice(1, -1); 以剥离第二部分(UTF/百分比编码),并且前导和尾随引号对我来说非常适合。 (假设文件名中没有分号!)【参考方案2】:

根据其他一些答案,您绝对可以使用 window.fetch 和 download.js 下载文件。但是window.fetch和blob一起使用有浏览器对内存的限制,而download.js也有its compatibility restrictions。

如果你需要下载一个大文件,你不会想把它放在客户端的内存中给浏览器带来压力吧?相反,您可能更喜欢通过流下载它。在这种情况下,使用 HTML 链接下载文件是最好/最简单的方法之一,尤其是通过流下载大文件时。

第一步:创建链接元素并设置样式

您可以使链接不可见但仍可操作。

HTML:

<a href="#" class="download-link" download>Download</a>

CSS:

.download-link 
  position: absolute;
  top: -9999px;
  left: -9999px;
  opacity: 0;

第二步:设置链接的href,并触发click事件

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()

注意事项

如有必要,您可以动态生成链接元素。 这种方法对于通过流下载在服务器端动态生成的大文件特别有用

【讨论】:

你怎么能让上面的 CSS 在 HTML 中内联? @JeffR,它会像这样:&lt;a href="#" class="download-link" style="position: absolute; top: -9999px; left: -9999px; opacity: 0" download&gt;Download&lt;/a&gt;,而这里的class属性仅供querySelector在JS代码中使用。 当我执行 'downloadLink.click()' 行时,我的 HTML 代码的其余部分消失了。有什么理由吗?如果我将“downloadLink.click()”注释掉并显示下载链接,则所有 html 都可以正常工作。有什么想法吗? @JeffR 可能与a HTML 元素的下载属性有关。此属性指示浏览器下载 URL 而不是导航到它,因此将提示用户将其保存为本地文件。此属性仅适用于同源 URL。也可以尝试不使用 '&amp;ts=' + new Date().getTime() 部分,看看它是否对您的情况有任何影响。【参考方案3】:

这样更短更高效,没有库只获取 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);
  );

【讨论】:

有没有办法设置文件名? 是的,您可以将 Content-Disposition 添加到 Headers obj,这是文档developer.mozilla.org/en-US/docs/Web/HTTP/Headers/… @LucasMatos 我在选项对象中添加了“Content-Disposition”标头,并且在网络选项卡中检查文件时确实获得了正确的标头,但是随后创建了 blob 并且名称被丢弃所以我最终得到了一个生成的随机名称。您知道如何将名称传递给您的解决方案吗? 这个gist.github.com/devloco/5f779216c988438777b76e7db113d05c 显示完整的蒙蒂。 @LucasMatos 我尝试编辑您的答案,但队列已满。为了给正在下载的文件声明一个名称,需要添加一个额外的行: var file = new File([blob], "filename.extension"); file = window.URL.createObjectURL(file);【参考方案4】:

我尝试了 window.fetch,但我的 REACT 应用程序最终变得复杂

现在我只需更改 window.location.href 并添加查询参数,例如 jsonwebtokenother 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)

最终结果实际上非常好,窗口发出请求并下载文件并且没有事件切换将页面移开,就好像window.location.href调用就像一个低调的fetch()调用。

【讨论】:

【参考方案5】:

使用下载js。这将从标题中解析文件名。

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

【讨论】:

这主要是有效的,但我最终改用了正则表达式from this other answer。所以...fileName = fileName.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1] ? fileName.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1] : fileName; Firefox 不支持filename.match(),我将那部分替换为:filename = response.headers.get("content-disposition").split(";")[1].split('"')[1];。此外,服务器必须声明头Access-Control-Expose-Headers: Content-Disposition,以允许浏览器读取content-disposition头。 这很有效。除了 Apple 落后于时代并且您的正则表达式在 Safari 上失败。 :( aizquier 的回答有效。【参考方案6】:

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

或:

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

【讨论】:

【参考方案7】:

编辑:syg 答案更好。只需使用downloadjs 库。

我提供的答案在 Chrome 上运行良好,但在 Firefox 和 IE 上,您需要此代码的一些不同变体。最好使用库。


我遇到了类似的问题(需要通过授权标头来下载文件,所以this 解决方案没有帮助)。

但基于this 的回答,您可以使用createObjectURL 让浏览器保存通过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         
        );
    );

【讨论】:

@David 我更新了答案。现在应该也可以在 FF 中工作。问题是,FF 希望将链接元素附加到 DOM。这在 Chrome 或 Safari 中不是强制性的。 最后调用 URL.revokeObjectURL 可以避免内存泄漏。 @AndrewSimontsev 很棒的提示,感谢您的意见!我编辑了回复,让我知道它是否正确。在我的代码上测试过,看起来还可以! 我不同意使用库是一个更好的答案:)。使用外部库并不总是一种选择,如果是,那么查找库很容易。这不值得回答,这可能就是为什么尽管接受的答案比接受的答案大 2 岁,但您的答案比接受的答案有更多的选票。 同样查看downloadjs上的代码,他们使用与temp相同的方法来保存文件。所以图书馆不一定能做得更好。【参考方案8】:

这是一个使用 node-fetch 的示例,供任何发现此内容的人使用。

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)

【讨论】:

这会保存到哪里? @迈克尔霍布斯 函数返回一个对象filename, blob,可以使用fs.writefile将文件保存到磁盘。【参考方案9】:

我暂时使用download.js和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);
  );

它适用于小文件,但可能不适用于大文件。我想我应该更多地挖掘Stream。

【讨论】:

请记住,当前浏览器的 blob 大小限制约为 500mb 很好,只有一件事:是否可以从服务器响应中获取文件名,以便让用户以真实姓名下载它? @Phate 要这样做,您应该从服务器传递一个对象,该对象不仅包含数据,还包含名称。一旦你这样做了,你可以单独处理它的字段,但是你应该在这里看到其他答案(据我了解).blob 方法是fetch 解析的对象之一

以上是关于如何使用 window.fetch 下载文件?的主要内容,如果未能解决你的问题,请参考以下文章

vue项目开发遇见bug

如何在 TypeScript 中使用 fetch

使用 window.fetch api 的预检请求失败

Kotlin使用window.fetch API获取二进制数据

@testing-library/与 msw/node 反应; window.fetch 未在 POST 上返回正文

如何使用 Angular 中浏览器的本机下载小部件以编程方式下载文件?