浏览器端下载那些事

1、背景

最近写了一个react的组件,用来作文件导出。环境是ie10+。
细一点说,就是javascript

  • 一、读取form里的数据html

  • 二、向服务端发请求,并下载文件;要求拿到请求状态,若是出错及时反馈给用户。html5

第一个需求,咱们借用了jquery的serializeArray方法,毕竟咱们不想再造轮子。那接下来重点说说后面的需求。java

2、通常下载文件方式

你们在下载的问题的时候,通常来讲,会用到react

  • 一、window.open(url);jquery

  • 二、window.location.href = url;web

  • 三、iframe,其实与window.open相似,但不用开启新的tabajax

  • 四、a 标签,利用download属性chrome

这些方法,其实极度依赖服务端的正确性。咱们能够看看服务端一旦出错的结果。api

  • 一、window.open(url)

    • 打开一个带错误信息的页面

  • 二、window.location.href

    • 页面将跳转到一个错误页面

  • 三、iframe

    • 用户感知不到任何变化

  • 四、a标签

    • 直接出现 下载失败

固然,若是response header里有content-disposition字段的话,浏览器都会下载一个带错误信息的文件。这时候,其实咱们能够多发一个ajax/fetch请求,先检测下接口状态,而后再取作下载逻辑。但这样就对服务器形成了额外的开销。

这样的体验都不太好,做为一个追求极致体验的程序猿,咱们应该从新思考下,如何提高用户体验。

3、浏览器FileAPI方式

相信你们对blob这个对象也不太陌生,它是html5标准里的一个二进制数据对象,能够与URL 对象配合,进行文件的下载。

下面是一个最简单的demo(咱们暂时不考虑浏览器兼容问题)。

let blob2 = new Blob(['123']);
let url = URL.createObjectURL(blob2);
let a = document.createElement('a');
a.download = 'test';
a.href = url;
a.click();

其实这样一个简单的demo,就能够实现浏览器端本身的下载了。那如何从服务端拿到数据,并下载呢?

这里,咱们拿服务端数据,主要是经过fetch,fetch提供了一些api。其中就有一个blob的promise,咱们能够把返回的数据转成blob对象,这样就能去下载文件了。

回到最初的需求,咱们须要检测接口的状态。其实经过fetch,咱们彻底能够拿到response的信息,既然都能拿到,那控制权就在咱们自手上了。

4、额外收获-进度条

按照FileApi的方式,咱们是一次性从服务端拿到数据,而后再在浏览器端进行操做。既然是这样,那拿数据的过程是否是就能够显示出进度呢?这个特性,咱们用之前传统的下载方式是彻底作不到的。

关于进度条,咱们能够利用fetch配合reader对象来实现进度条功能,以下:

fetch(url).then(response => {

  var reader = response.body.getReader();
  var headers = response.headers;
  var totalLength = headers.get('Content-Length');
  var bytesReceived = 0;

  reader.read().then(function processResult(result) {
    if (result.done) {
      return;
    }

    bytesReceived += result.value.length;
    console.log(`progress: ${bytesReceived / totalLength * 100}%`);

    return reader.read().then(processResult);
  });
});

固然,有人可能会说ie下fetch会有问题。没错,确实会有问题,但这时候咱们能够用XMLHttpRequest这个对象来实现,会更简单直接一点。

5、常见问题

一、filename

经过fileapi的方式下载文件,有个很重要的问题,就是文件名。最初的一些下载方式,都是浏览器本身经过判断content-disposition这个字段来读取文件名。那如今不同了,咱们须要本身来读取文件名,这时候不免要本身读这个header,经过正则匹配下文件名。

二、cors

关于cors问题,其实只要是异步请求,都会碰到。新版浏览器,咱们经常使用access-control-allow-origin这个字段来解决跨域问题。这时候,咱们在读文件名的可能要留一点,记得在header里加上Access-Control-Expose-Headers这个字段,否则fetch是取不到filename信息的。具体能够看看这篇doc

三、大文件下载问题

在用fileapi的时候,咱们发现文件过大会让浏览器崩溃,会致使文件下载失败。目前我在测试500mb以上的文件的时候就会碰到这样的状况。这个问题,能够经过webkitrequestfilesystem这个对象来曲线解决,但这并非一个standard api,目前只有新版chrome支持这个对象,因此尽可能不要去用。

咱们推荐在拿到response的时候,读取一下blob的size,若是发现太大,就进行降级处理,使用咱们最初的那中方式。

5、总结

我一直相信no silver bullet这句话,虽然fileapi这种方式能解决部分问题,但其实也有不少缺点,相信你们会在实际场景中会更深入的感觉到。因此在设计组件的时候,咱们在作好优雅降级的方案同时,还特地为你们开放了各类下载方式,以适应各类场景。

相关文章
相关标签/搜索