fetch请求下载文件,内含主要步骤和实现代码

7,923 阅读2分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

代码自取,欢迎评论区沟通交流

一.Fetch是什么?

  • Fetch API 提供了一个获取资源的接口
  • 同时规范了Request[1]和Reponse[2]对象的通用定义。

二.Fetch下载文件的过程

  • 既然是请求,那么一定会涉及到两个东西,请求和响应。
  • 接下来我们就分析下请求和响应
  1. 请求:通常下载文件请求就是常见的get和post请求
  2. 响应:Fetch API的 Response 对象就是响应的数据,也是本次需要处理的对象。用到的属性有
    • Response.headers[3]:响应头信息,此处用来获取文件的名字
filename = decodeURIComponent(res.headers.get("content-disposition").replace("attachment;filename=", ""));
  • Response.ok[4]:布尔值,是否响应成功
if(res.ok){
    return res
}else{
    // 错误处理
}
  • Response.body[5]:响应的内容,ReadableStream[6]类型,通过getReader()[7]方法创建一个ReadableStreamDefaultReader,按顺序读取。读取完成后,需要创建一个新的ReadableStream,用于生成Response,进行传递res,方便后续处理
const readStream = reader => new ReadableStream({
    start(controller) {
        return push();
        function push() {
            return reader.read().then((res) => {
                const { done, value } = res;
                if (done) {
                    controller.close();
                }
                controller.enqueue(value);
                return push();
            });
        }
    },
});
  • 生成的ReadableStream用于返回一个新的Response,进行传递处理

三.转化成blob,以下方法等同于res.blob()

  • 推荐使用res.blob(),如果正常运行,直接使用即可,使用方法等同于res.json()。这里是遇到res.blob()偶现解析不成功。
const resBlob = (reader, data, type) => new Promise((resolve) => {
    function push() {
        reader.read().then(({ done, value }) => {
            data.push(value);
            done ? resolve(new Blob(data, { type })) : push();
        });
    }
    push();
});

四.保存文件

  • 通过FileReader[8]读取刚刚的blob数据,等到loadend事件,读取完成,进行下载操作
  • reader.readAsText(blob): 开始读取指定的blob内容
const savingFile = (blob, fileName) => {
    const reader = new FileReader();
    reader.addEventListener('loadend', () => downloadBlob(blob, fileName));
    reader.readAsText(blob);
};

五.下载文件

  • 传入blob和文件名,进行下载操作
const downloadBlob = (blob, fileName) => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.target = '_blank';
    a.style.display = 'none';
    document.body.appendChild(a);
    a.download = fileName;
    a.click();
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
};

六.使用举例

  • 设置参数
    • credentials:是否携带cookie
    • headers:请求头,请求的格式是什么
const option = {
    credentials: 'include',
    headers: new Headers({
        'Content-Type': 'application/x-www-form-urlencoded',
    }),
};
// 下载
// payload:fetch请求的参数
const download = (payload, filename = "默认文件名.xlsx") =>
    fetch(`request/url?${qs.stringify(payload)}`, option)
        .then((res) => {
            // 设置文件名,从响应头中获取
            filename = decodeURIComponent(res.headers.get("content-disposition").replace("attachment;filename=", ""));
            return new Response(readStream(res.body.getReader()));
        })
        .then((res) =>resBlob(res.body.getReader(), [], res.headers.get("Content-Type")))
        .then((response) => savingFile(response, filename))
        .catch((error) => message.warning(`${error.message}请再次尝试`));

七.参考文档

  1. Request
  2. Response
  3. Response.header
  4. Response.ok
  5. Response.body
  6. ReadableStream
  7. ReadableStream.getReader()
  8. FileReader