• 下载文件报错:TypeError: window.URL.createObjectURL is not a function
  • 发布于 2个月前
  • 1053 热度
    0 评论
上次被后端欺负的,还是在数据分页的时候,到底是前端分页还是后端分页上,当时我咬着后槽牙,硬生生的做了前端分页。时隔几年之后,又一次遇到了类似的事情。最开始的时候,项目内需要添加一个下载功能,当初与后端约定的是,后端返回文件流 ,前端直接将返回流保存成xls文件,开始的时候,风平浪静。于是,这样的代码诞生了:
<template>
    <el-button type="primary" @click="download">下载</el-button>
</template>

<script>
const DOWNLOAD_FILE_NAME = '下载文件名称'
import { downloadApi } from "@/api"
export default {
    methods:{
       download(){
           downloadApi().then(response => {
              let blob = new Blob([response],{type:"application/vnd.ms-excel;charset=utf-8"});
              let url = window.URL.createObjectURL(blob); // 创建 url 并指向 blob
              let a = document.createElement('a');
              a.href = url;
              a.download = `${DOWNLOAD_FILE_NAME}.xls`;
              a.click();
              window.URL.revokeObjectURL(url); // 释放该 url             
           }).catch(err => {
               this.$message.error(err)           
           })  
       } 
    }
}
</script>
相安无事了一段时间后,测试的小伙伴发现,有些时候,会出现下载失败,查看之后,出现了这样的错误
TypeError: window.URL.createObjectURL is not a function
后来经过控制台调试,使用上面方式下载的时,有的时候,window.URL.createObjectURL是undefined。这,突然出现这样的问题,重新找解决方案,可能还有些紧张,于是我找到了同部门的小伙伴,参考之前的项目,匆匆忙忙在项目中引入了js-file-download。

于是修改了代码成这样
// 堆代码 duidaima.com
<template>
    <el-button type="primary" @click="download">下载</el-button>
</template>

<script>
const DOWNLOAD_FILE_NAME = '下载文件名称'
import { downloadApi } from "@/api"
import fileDownload from "js-file-download";
export default {
    methods:{
       download(){
          downloadApi().then(response => {
                 fileDownload(response, `${DOWNLOAD_FILE_NAME}.xls`)       
          })
       } 
    }
}
</script>
随着数据量的增加,提出了新的需求,当前用户下载的时候,不允许同时再下载文件,因为返回的是流,前端没有办法按钮的Loading什么时候结束,于是后端在 用户正在下载的时候,会返回json,并约定好,如果返回的内容为json,就认为用户正在下载 ,具体的提示信息,就从json中获取。

因为前端使用返回流保存文件的时候,需要设置请求的类型为Blob,在后端 返回json的时候,前端同样会认为这个json数据就是Blob,并会将返回的json写入到Excel中。再加上返回json的速度明显快于流 的返回速度 ,导致每次需要的文件还没下载完,反而下载了很多含有json的Excel
@/api/index.js

export function downloadApi() {
  return request({
    url: process.env.VUE_APP_BASE_API +'/api/download',
    method: 'post',
    responseType: 'blob'
  })
}
最后,考虑到是不是 可以将返回的内容进行转换,通过JSON.parse处理,处理成功并且json中存在code,就认为用户正在下载,直接展示提示信息,最后下载功能告一段落了,贴上最后代码
<template>
    <el-button type="primary" @click="download">下载</el-button>
</template>

<script>
const DOWNLOAD_FILE_NAME = '下载文件名称'
import { downloadApi } from "@/api"
import fileDownload from "js-file-download";
export default {
    methods:{
       download(){
          downloadApi().then(response => {
          this._handleDownResp(response)
          }).catch(err => {
              this.$message.error(err);
          })
       },
       _handleDownResp(response){
          const file = new FileReader();
          file.readAsText(response, 'utf-8');
          const THIS = this
          file.onload = function () {
              try{ // 防止出现转换json错误,导致下载失败
                const RES_OBJ = JSON.parse(file.result);
                if(!!RES_OBJ.code){
                THIS.$message({
                  message: RES_OBJ.data,
                  type: 'error'
                });
              }
              }catch(e){ // 转换json失败,直接将流文件保存为文件
                THIS._saveFile(response)
              }
           }
        }
    }
}
</script>
最后小编再说一句,上面的情况并不完全合理,实际应该加一个验证是否文件下载完成的接口,每次下载之前,根据接口返回的内容,前端判断是展示提示信息还是下载文件更为合理
用户评论