Web页面中文件下载的几种方式

500#lx13l02j56pdtb6jyvx

页面中下载文件最建议的还是直接使用浏览器的下载器,作为桌面程序拥有文件的控制权限,可以随时暂停、继续下载,而它的一个缺点是,无法下载非get请求的接口提供的文件。

浏览器直接接管

这种方式通常通过创建一个a标签来,将需要下载的文件链接放到它的href属性上,设置download属性让浏览器将链接的 URL 视为下载资源。

js 复制代码
const fileUrl = 'http://eg.com/file.zip';

// 创建一个隐藏的可下载链接
const downloadLink = document.createElement('a');
downloadLink.style.display = 'none';
downloadLink.download = 'file.zip';
// 不需要将它嵌入页面
downloadLink.click();

优点:

  1. 代码量少,简洁。
  2. 浏览器接管下载过程,随时可以暂停、继续。

缺点:

  1. 只支持GET请求的链接。
  2. 带权限的链接需要添加权限验证信息到链接上。
  3. 图片等资源的链接需要在服务端接口设置该链接的Content-Dispositionattachment,而非inline。不然会直接在浏览器上打开。

xhr或fetch获取文件

这种方式属于先调用接口,再保存。保存的方式分为创建URL对象使用浏览器下载器保存调用现在浏览器接口直接保存

这种方式同样支持分片下载,不过不同的是无论是什么保存方式,都无法暂停后继续保存后续分片到当前文件中。

分片下载的方式有两种:

  1. 接口参数,例如下载链接为:http://eg.com/file.zip?patch=1,通过传递参数告诉接口需要下载的是哪块分片,类似m3u8视频原理。不过在下载文件中不建议使用。
  2. 请求头Range,原理相同,方式不同,这种方式预先发起一个head请求,得到待下载文件的大小(Content-Length),然后循环发起分片请求并设置请求头Range: bytes=${start}-${end},告诉服务器需要下载那部分内容,控制权在客户端。

创建URL对象保存

这种保存方式需要整个文件下载完成后才能保存,分布分片都无所谓了。

js 复制代码
fetch(fileUrl)
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.blob();
  })
  .then(blob => {
    // 创建一个URL表示这个blob对象
    const url = window.URL.createObjectURL(blob);
    downloadLink.href = url;
    downloadLink.download = 'file.zip';
    downloadLink.click();
    // 释放创建的URL对象
    window.URL.revokeObjectURL(url);
  })
  .catch(error => {
    console.error('下载文件时出错:', error);
  });

优点:

  1. 可以使用项目封装的接口请求工具,不限制请求方式,不限制权限。

缺点:

  1. 需要将整个文件保存在内存中。

showSaveFilePicker

js 复制代码
const fileHandle = await window.showSaveFilePicker({
  suggestedName: 'file.zip'
});
const writableStream = await fileHandle.createWritable();


// 分片
const response = await fetch(fileUrl, {
  headers: {
    Range: `bytes=${start}-${end}`
  }
});

if ((response.ok || response.status === 206) && response.body) {
  const reader = response.body.getReader();
  let done = false;
  while (!done && !pauseRequested && !cancelRequested) {
    const { value, done: chunkDone } = await reader.read();
    if (value) {
      await writableStream.write(value);
      downloadedSize += value.length;
    }
    done = chunkDone;
  }
}

优点:

  1. 不必将整个文件存在内存中,分片下载每片后,即可实时写入目标文件。

缺点:

  1. 浏览器兼容极差

参与本文讨论

请先登录 GitHub 后留言

0/500

本文留言

1
  • imzbf
    imzbf

    考虑兼容性的话,showSaveFilePicker根本没法用

    暂无回复

1 / 1