前言

欢送关注同名公众号《熊的猫》,文章会同步更新,也可疾速退出前端交换群!

本文灵感源于上周小伙伴遇到一个问题:

"一个本该返回 Blob 类型的下载接口,却返回了 JSon 类型的内容!!!"

这会有什么问题呢?

按原逻辑就是调用该接口后,就会一股脑把该接口接返回过去的内容,间接通过 Blob 对象 转换后再通过暗藏的 <a> 标签实现下载。

然而有一个问题,那就是接口也是须要进行各种逻辑解决、判断等等,而后再决定是给前端响应一个失常的 Blob 格局的文件流,还是返回相应 JSon 格局的异样信息 等等。

如果返回了 JSon 格局的异样信息,那前端应该给用户展现信息内容,而不是将其作为下载的内容!

FileReader 实现 Blob 从 String 到 JSON

复现问题

为了更直观看到对应的成果,咱们这里来简略模仿一下前后端的交互过程吧!

前端

因为小伙伴发送申请时应用的是 Axios,并且设置了其对应的 responsetype:blob | arraybuffer,所以这里咱们也应用 Axios 即可,具体如下:

    // 发动申请    const request = () => {      axios({        method: 'get',        url: 'http://127.0.0.1:3000',        responseType: 'arraybuffer'      })        .then((res) => {          // 转换为 bloc 对象          const blob = new Blob([res.data])          // 获取导出文件名,decodeURIComponent为中文解码办法          const fileName = decodeURIComponent(res.headers["content-disposition"].split("filename=")[1])          // 通过a标签进行下载          let downloadElement = document.createElement('a');          let href = window.URL.createObjectURL(blob);          downloadElement.href = href;          downloadElement.download = fileName;          document.body.appendChild(downloadElement);          downloadElement.click();          document.body.removeChild(downloadElement);          window.URL.revokeObjectURL(href);        });    }

后端

这里咱们就简略通过 koa 来实现将一个表格文件响应给前端,具体如下:

    const xlsx = require("node-xlsx");    const Koa = require("koa");    const app = new Koa();    const cors = require("koa2-cors");    // 解决跨域    app.use(      cors({        origin: "*", // 容许来自指定域名申请        maxAge: 5, // 本次预检申请的有效期,单位为秒        methods: ["GET", "POST"], // 所容许的 HTTP 申请办法        credentials: true, // 是否容许发送 Cookie      })    );    // 响应    app.use(async (ctx) => {      // 文件名字      const filename = "人员信息";      // 数据      const data = [        { name: "赵", age: 16 },        { name: "钱", age: 20 },        { name: "孙", age: 17 },        { name: "李", age: 19 },        { name: "吴", age: 18 },      ];      // 表格款式      const oprions = {        "!cols": [{ wch: 24 }, { wch: 20 }, { wch: 100 }, { wch: 20 }, { wch: 10 }],      };      // JSON -> Buffer      const buffer = JSONToBuffer(data, oprions);      // 设置 content-type      ctx.set("Content-Type", "application/vnd.openxmlformats");      // 设置文件名,中文必须用 encodeURIComponent 包裹,否则会报异样      ctx.set(        "Content-Disposition",        "attachment; filename=" + encodeURIComponent(filename) + ".xlsx"      );      // 文件必须设置该申请头,否则前端拿不到 Content-Disposition 响应头信息      ctx.set("Access-Control-Expose-Headers", "Content-Disposition");      // 将 buffer 返回给前端      ctx.body = buffer;    });    // 将数据转成 Buffer    const JSONToBuffer = (data, options = {}) => {      let xlsxObj = [        {          name: "sheet",          data: [],        },      ];      data.forEach((item, idx) => {        // 解决 excel 表头        if (idx === 0) {          xlsxObj[0].data.push(Object.keys(item));        }        // 解决其余 excel 数据        xlsxObj[0].data.push(Object.values(item));      });      // 返回 buffer 对象      return xlsx.build(xlsxObj, options);    };        // 启动服务    app.listen(3000);

失常成果展现

异样成果展现

能够看到当返回的内容为 JSON 格局 的内容时,本来逻辑在获取 filename 处就产生异样了,即便这一块没有产生异样,被失常下载下来也是不对的,因为这种状况应该要进行提醒。

并且此时间接去拜访 res.data 失去的也不是一个 JSON 格局 的内容,而是一个 ArrayBuffer

返回的明明是 JSON ,然而拿到的却是 ArrayBuffer?

responseType 惹的祸

还记得咱们在通过 Axios 去发动申请时设置的 responseType:'arraybuffer' 吗?

没错,就是因为这个配置的问题,它会把失去的后果给转成设置的类型,所以看起是一个 JSON 数据,但实际上拿到的是 Arraybuffer

这个 responseType 实际上就是 XMLHttpRequest.responseType,可点击该链接自行查看。

不设置 responseType 行不行?

那么既然是这个配置的问题,那么咱们不设置不就好了!

的确可行,如下是未设置 responseType 获取到的后果:

但也不行,如果不设置 responseType 或者设置的类型不对,那么在 失常状况 下(即 文件被下载)时 会导致文件格式被损坏,无奈失常关上,如下:

FileReader 来救场

实际上还有个比拟间接的解决方案,那就是把接管到的 Arraybuffer 转成 JSON 格局不就行了吗?

没错,咱们只须要通过 FileReader 来实现这一步即可,请看如下示例:

// json -> blobconst obj = { hello: "world" };const blob = new Blob([JSON.stringify(obj, null, 2)], {  type: "application/json",});console.log(blob) // Blob {size: 22, type: 'application/json'}// blob -> jsonconst reader = new FileReader()reader.onload = () => {    console.log(JSON.parse(reader.result)) // { hello: "world" }}reader.readAsText(blob,  'utf-8')

是不是很简略啊!

值得注意的是,并不是任何时候都须要转成 JSON 数据,就像并不是任何时候都要下载一样,咱们须要判断什么时候该走下载逻辑,什么时候该走转换成 JSON 数据。

怎么判断以后是该下载?还是该转成 JSON?

这个还是比较简单的,换个说法就是判断以后返回的是不是文件流,上面列举较常见的两种形式。

依据 filename 判断

失常状况来讲,再返回文件流的同时会在 Content-Disposition 响应头中增加和 filename 相干的信息,换句话说,如果以后没有返回 filename 相干的内容,那么就能够将其当做异常情况,此时就应该走转 JSON 的逻辑。

不过须要留神,有时候后端返回的某些文件流并不会设置 filename 的值,此时尽管合乎异常情况,然而实际上返回的是一个失常的文件流,因而不太举荐这种形式

依据 Content-Type 判断

这种形式更正当,毕竟后端无论是返回 文件流 或是 JSON 格局的内容,其响应头中对应的 Content-Type,必然不同,这里的判断更简略,咱们直接判断其是不是 JSON 类型即可。

更改后的代码,如下:

axios({    method: 'get',    url: 'http://127.0.0.1:3000',    responseType: 'arraybuffer'  })    .then(({headers, data}) => {      console.log("FileReader 解决前:", data)      const IsJson = headers['content-type'].indexOf('application/json') > -1;      if(IsJson){        const reader = new FileReader()                // readAsText 只接管 blob 类型,因而这里须要先将 arraybuffer 变成 blob        // 若后端间接返回的就是 blob 类型,则间接应用即可        reader.readAsText(new Blob([data], {type: 'application/json'}), 'utf-8')                reader.onload = () => {          // 将字符内容转为 JSON 格局          console.log("FileReader 解决后:", JSON.parse(reader.result))        }        return      }      // 下载逻辑      download(data)    });

值得注意的是,readAsText 只接管 blob 类型,因而这里须要先将 arraybuffer 变成 blob,若后端间接返回的就是 blob 类型,则间接应用即可。

FileReader 还能干什么?

以上是应用 FileReader 解决一个理论问题的例子,那么除此之外它还有什么利用场景呢?

不过咱们还是先来理解一下 FileReader 的一些相干内容吧!!!

FileReader 是什么?

FileReader 对象容许 Web 应用程序 异步读取 存储在用户计算机上的文件(或原始数据缓冲区)的内容,应用 File 或 Blob 对象指定要读取的文件或数据。

不过还要留神如下两条规定:

  • FileReader 仅用于以平安的形式从用户(近程)零碎读取文件内容,它不能用于从文件系统中按路径名简略地读取文件
  • 要在 JavaScript 中按路径名读取文件,应应用规范 Ajax 解决方案进行 服务器端文件读取

总结起来就是,FileReader 只能读取 FileBlob 类型的文件内容,并且不能间接按门路的形式读取文件,如果须要以门路形式读取,最好要通过 服务端 返回流的模式。

四种读取形式

FileReader 能够如下四种形式读取指标文件:

  • FileReader.readAsArrayBuffer()

    • 开始读取指定的 Blob中的内容,读取实现后,result 属性中保留的将是被读取文件的 ArrayBuffer 数据对象
  • FileReader.readAsBinaryString() (非标准

    • 开始读取指定的Blob中的内容,读取实现后,result 属性中将蕴含所读取文件的 原始二进制数据
  • FileReader.readAsDataURL()

    • 开始读取指定的Blob中的内容,读取实现后,result 属性中将蕴含一个 data: URL 格局的 Base64 字符串以示意所读取文件的内容
  • FileReader.readAsText()

    • 开始读取指定的Blob中的内容,读取实现后,result 属性中将蕴含一个 字符串 以示意所读取的文件内容

如上对应的办法命名非常合乎顾名思义的特点,因而能够很容易看进去在不同场景下应该抉择什么办法,并且如上办法个别都会配合 FileReader.onload 事件FileReader.result 属性 一起应用。

FileReader 的其余利用场景

预览本地文件

通常状况下,前端抉择了相应的本地文件(图片、音/视频 等)后,须要通过接口发送到服务端,接着服务端在返回一个相应的预览地址,前端在实现反对预览的操作。

如果说当初有一个须要省略掉两头过程的需要,那么你就能够通过 FileReader.readAsDataURL() 办法来实现,然而要思考文件大小带来转换工夫快慢的问题。

这一部分比较简单,就不贴代码占篇幅了,成果如下:

传输二进制格局数据

通常在上传文件时,前端间接将接管到的 File 对象以 FormData 发送给后端,但如果后端须要的是二进制的数据内容怎么办?

此时咱们就能够应用 FileReader.readAsArrayBuffer() 来配合,为啥不必 FileReader.readAsBinaryString(),因为它是非规范的,而且 ArrayBuffer 也是原始的 二进制数据

具体代码如下:

// 文件变动const fileChange = (e: any) => {  const file = e.target.files[0]  const reader = new FileReader()  reader.readAsArrayBuffer(file)  reader.onload = () => {    upload(reader.result, 'http://xxx')  }}// 上传const upload = (binary, url) => {  var xhr = new XMLHttpRequest();  xhr.open("POST", url);  xhr.overrideMimeType("application/octet-stream");  //间接发送二进制数据  xhr.send(binary);  // 监听变动  xhr.onreadystatechange = function (e) {    if (xhr.readyState === 4) {      if (xhr.status === 200) {        // 响应胜利             }    }  }}

最初

欢送关注同名公众号《熊的猫》,文章会同步更新,也可疾速退出前端交换群!

下面咱们通过 FileReader 解决了一个理论问题,同时也简略介绍了其相应的应用场景,但这个场景具体是否是用于你的需要还要具体分析,不能自觉应用。

以上就是本文的全部内容了,心愿本文对你有所帮忙!!!