目录
在Web开发中,我们经常会遇到需要处理文件下载的情况。特别是在处理API响应时,有时我们需要根据响应的内容类型和状态码来决定如何处理数据。本文将详细解析一段用于处理文件下载的JavaScript代码,该代码优先尝试将响应解析为JSON错误信息,若解析失败则按正常文件进行下载。
代码详解
- 从API响应中读取数据。
- 尝试将响应内容解析为JSON格式,以判断是否为错误信息。
- 根据解析结果决定继续处理还是直接下载文件。
- 如果确定为文件,则创建Blob对象并触发浏览器下载。
javascript
// 下载文件并处理响应:优先尝试解析为 JSON 错误信息,若解析失败则按正常文件下载
const downLoadFileBlob = (res, fileName, type = 'application/vnd.ms-excel;charset=utf-8', validateErrorCode) => {
const reader = new FileReader()
// 将响应数据读取为文本,以便后续尝试 JSON 解析
reader.readAsText(res.data)
reader.onload = () => {
try {
// 尝试将响应文本解析为 JSON,判断是否为错误响应
const response = JSON.parse(reader.result)
console.log(response)
// 若外部传入自定义错误码校验函数且校验通过,则直接返回,不再处理
if (validateErrorCode && validateErrorCode(response.errorCode)) return
// 错误码 401 表示登录失效,清空本地缓存并跳转登录页
if (response.errorCode === 401) {
localStorage.clear()
window.location.href = '/login'
return
}
// 若响应头指示为 JSON 文件,则主动抛出异常,进入 catch 分支进行文件下载
if (res.headers['content-disposition'].indexOf('.json') !== -1) {
throw new Error('文件解析失败,直接下载')
} else {
// 其他情况弹窗提示接口错误
alert('接口错误')
}
} catch (err) {
// 捕获异常后,进入正常文件下载流程
console.log('下载正常处理')
var downFileName = fileName
try {
// 从响应头 content-disposition 中提取文件名并解码
downFileName = res.headers['content-disposition'].split(';')[1].split('=')[1]
downFileName = decodeURIComponent(downFileName)
} catch {
// 若提取失败,使用默认传入的文件名
console.log('file name is empty')
}
// 获取文件扩展名,用于决定 Blob 的 MIME 类型
let fileArr = downFileName.split('.')
let extensionName = fileArr[fileArr.length - 1]
let blob
if (extensionName === 'zip') {
// ZIP 文件使用 Excel MIME 类型(兼容旧逻辑)
blob = new Blob([res.data], { type: 'application/vnd.ms-excel;charset=utf-8' })
} else if (extensionName === 'json') {
// JSON 文件使用 JSON MIME 类型
blob = new Blob([res.data], { type: 'application/json;charset=utf-8' })
} else {
// 其他文件使用外部传入或默认 MIME 类型
blob = new Blob([res.data], { type: type })
}
// 创建 Blob URL 并触发浏览器下载
var url = window.URL.createObjectURL(blob)
var aLink = document.createElement('a')
aLink.style.display = 'none'
aLink.href = url
aLink.setAttribute('download', downFileName)
document.body.appendChild(aLink)
aLink.click()
// 下载完成后移除临时元素并释放 Blob URL
document.body.removeChild(aLink)
window.URL.revokeObjectURL(url)
}
}
}
方法接收四个参数,分别如下
res: API响应对象,包含data和headers等属性。fileName: 默认文件名。type: Blob对象的MIME类型,默认值为'application/vnd.ms-excel;charset=utf-8'。validateErrorCode: 自定义的错误码校验函数。
FileReader对象用于异步读取Blob或File对象的内容。这里我们将响应数据res.data作为文本读取,以便后续尝试将其解析为JSON,为什么要做这部操作?直接解析Blob不就行了吗?现实中肯定不会这里简单,假如点击下载时登录失效,这个时候返回的时{errorCode : 401, errorMsg: '登录失效'},这个时候再按照Blob解析就会报错,不会退出登录,如果提供了validateErrorCode函数,则需要在validateErrorCode中自己处理错误,如果不提供,可以在下面处理公共错误。
如果JSON解析失败,证明时数据流了(不是JSON或数据流自行处理),从Content-Disposition头部提取文件名,并进行URL解码,创建Blob对象,创建隐藏的<a>标签,设置其href为Blob URL,并通过click事件触发下载,下载完成后,移除临时创建的<a>标签,并释放Blob URL。
本地开发可以拿到Content-Disposition字段,部署之后拿不到怎么办?请移步另一篇博文,前端无法获取响应头(如 Content-Disposition)的原因与解决方案。
附
- API封装
javascript
export const downloadFile = params => {
return request.post(`${process.env.REACT_APP_API_URL}/api`, params, {
// hiddenError: true,
// noSpin: true,
responseType: 'blob',
uploadOrDownload: true
})
}
- API调用
javascript
downloadFile(params)
.then(res => {
// 后续会补充downLoadFileBlob方法
downLoadFileBlob(res)
})