Skip to content

axios获取responseType为blob时请求的错误信息

前言

在前后端交互中,文件下载(导出)是很常规的操作,假设前端用axios进行文件下载,需要在请求中加入responseType:blob来设置以流的形式进行响应,这样之后,请求成功时返回的是一个流形式的文件,正常导出文件。但是请求失败的时候后端返回的是json ,不会处理错误信息,而是直接导出包含错误信息的文件。用户体验不是很好,我想要的效果是,请求成功时候正常下载文件,请求失败时不下载文件,仅仅将错误信息展示出来。

1. 思路

通常在封装的axios中我们都是以后端返回的code值进行判断,因此就没有办法获取到后端返回的错误信息进行提示

解决方案邮很多,这里说两种:

  • 第一种, 默认responseTypejson,然后请求成功之后将json格式转化成blob在进行导出
  • 第二种,设置responseType = "blob",请求失败之后将blob转化成json 格式

这里用第二种方式进行举例

2. axios设置responseType:'blob'

2.1 对某个请求设置

js
axios({
  method: "请求方式",
  url: "接口路径",
  data: "接口参数",
  dataType: 'json',
  responseType: 'blob',
  headers: {},
  data:{},  // body参数
  params:{} // queryString参数
})
.then(response => {})
.catch(error => {})

2.2 统一设置

js
const service = axios.create({
 baseURL:"xxxxx",
 responseType:"blob"
})

// 或者
service.defaults.responseType = 'blob'

3 获取错误Msg

3.1 针对某个请求获取

js
// 省略接口请求
// .......
getData(params).then(response => {
  const resData = response.data
  const fileReader = new FileReader()
  fileReader.onloadend = () => {
    // 此处两种判断返回信息的方法可选其一
    // 方法一:
    try {
      const jsonData = JSON.parse(fileReader.result) // 说明是普通对象数据,后台转换失败
      // 后台信息
      console.log(jsonData)
    } catch (err) { // 解析成对象失败,说明是正常的文件流
      // 下载文件
      downloadFile(resData)
    }
 
    // 方法二:
    if (resData.type === 'application/json') {
      const jsonData = JSON.parse(fileReader.result) // 说明是普通对象数据,后台转换失败
      // 后台信息
      console.log(jsonData)
    } else {
      // 下载文件
      downloadFile(resData)
    }
  }
  fileReader.readAsText(resData)
})
 
function downloadFile (resData) {
  const link = document.createElement('a')
  let blob = new Blob([resData], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'})
  link.style.display = 'none'
  link.href = URL.createObjectURL(blob)
  link.setAttribute('download', '下载文件.xlsx') // 文件名可自定义
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}

jsonData就是后台的报错信息。如果使用第二种方法,建议改为下面的代码,即下载文件时不需要FileReader

js
// 省略接口请求
// .....
getData(params).then(response => {
  const resData = response.data
  if (resData.type === 'application/json') {
    // 说明是普通对象数据,读取信息
    const fileReader = new FileReader()
    fileReader.onloadend = () => {
      const jsonData = JSON.parse(fileReader.result)
      // 后台信息
      console.log(jsonData)
    }
    fileReader.readAsText(resData)
  } else {
    // 下载文件
    downloadFile(resData)
  }
})

3.2 拦截器统一获取

js
// 响应拦截器
service.interceptors.response.use(
  response => {
    if (response.headers['content-type'] === 'application/json') {
      const res = response.data
        // 针对文件下载失败时候的处理
      if (res.size) { 
        // 将blob转为json
        const reader = new FileReader()
        let parseObj = null
        reader.readAsText(response.data, 'utf-8')
        reader.onload = function() {
          parseObj = JSON.parse(reader.result)
          Message.error(parseObj.message || 'Error')
        }
        return Promise.reject(new Error(res.message || 'Error'))
      } else {
        if (res.code !== '200') {
          // Message({
          //   message: res.message || 'Error',
          //   type: 'error',
          //   duration: 5 * 1000
          // })
          Message.error(res.message || 'Error')
          // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
          if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
            // to re-login
            MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
              confirmButtonText: 'Re-Login',
              cancelButtonText: 'Cancel',
              type: 'warning'
            }).then(() => {
              store.dispatch('user/resetToken').then(() => {
                location.reload()
              })
            })
          }
          return Promise.reject(new Error(res.message || 'Error'))
        } else {
          return res
        }
      }
    } else {
      return response
    }
  })

4. 文件下载公共方法

注意

一些内置的变量以及方法,不要可以删除,Vue2里面, 可以用mixin来处理更为方便, vue3用hook更为方便

js
/**
 * 通过APi接口下载文件的通用方法
 * @Description 传入接口以及入参, 自动下载文件<br>
 * 1. 内置 <b>this.downloadLoading</b> 状态,可用于控制下载按钮是否处于loading状态<br>
 * 2. 内置操作日志上报接口<br>
 * 3. 可将下载接口返回JSON时候的msg提示出来
 * @param {*}  method  文件下载请求方法接口
 * @param {*} data 接口入参
 * @param {*} addLogParams 操作日志入参(内部有默认值)
 * @returns 
 */
downLoadFileByApi(method, data = {}, addLogParams = {}) {
  if (Object.prototype.toString.call(method) !== "[object Function]") {
    throw "method参数必须是一个Function";
  }
  addLogParams = {
    subFunction: "导出",
    type: "exportFile",
    result: data,
    isSuccess: "1",
    ...addLogParams
  };
  // this.downloadLoading = true; // 禁用下载按钮
  method(data)
    .then(response => {
      // response是axios返回的response, 没经过处理
      const resData = response.data;
      // 说明是普通对象数据,读取信息
      if (resData.type === "application/json") {
        const fileReader = new FileReader();
        fileReader.readAsText(resData);
        fileReader.onloadend = () => {
          const jsonData = JSON.parse(fileReader.result);
          // 后台信息
          const { code, msg } = jsonData;
          if (code === 0) {
            this.$message.waning(msg);
          } else {
            this.$message.error(msg);
          }
          // this.downloadLoading = false; // 解除禁用下载按钮
          // this.handleLogFunction(addLogParams);
        };
      } else {
        let filename = decodeURIComponent(
          response.headers["content-disposition"]
        );
        filename = filename.split("=")[1].replace("UTF-8''", "").trim();
        const blob = resData;
        let link = document.createElement("a");
        link.style.display = "none";
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        URL.revokeObjectURL(link.href); //释放掉blob对象
        document.body.removeChild(link); //下载完成移除元素
        // this.$message({ message: '操作成功', type: 'success' });
        // this.downloadLoading = false; // 解除禁用下载按钮
        // addLogParams.isSuccess = '0'
        // this.handleLogFunction(addLogParams);
      }
    })
    .catch(error => {
      // this.$message.error('error.message)
      // this.downloadLoading = false; // 解除禁用下载按钮
      // addLogParams.isSuccess = error.isSuccess ? error.isSuccess : '1';
      // this.handleLogFunction(addLogParams);
    });
}

文件下载参考

js
// get 请求
axios({
    method: 'get',
    url,   // 这里自行设置传参
    params, // 这里自行设置传参
    responseType:'blob' // 这里是重点,敲黑板
}).then(res => {
  // 获取响应头中的文件名
  let filename = response.headers  //下载后文件名
  filename = filename["content-disposition"]
  filename = filename.split(";")[1].split("filename=")[1]
  // 创建 a 标签
  let elink = document.createElement('a');
  document.body.appendChild(elink);
  elink.style.display = 'none'; // 隐藏起来
  elink.download = filename
  // 如果后端没有返回,可以自己设置下载文件的名称 elink.download = 'XXX文件.pdf'; 
  let blob = new Blob([res.data]);
  // 兼容webkix浏览器,处理webkit浏览器中herf自动添加blob前缀,默认在浏览器打开而不是下载
  const URL = window.URL || window.webkitURL
  elink.href = URL.createObjectURL(blob) // 通过createObjectURL方法转换成对象url
    
  elink.click();
  document.body.removeChild(elink);
  URL.revokeObjectURL(elink.href); // 释放URL 对象
})