场景:
在一个系统里,用户点击下载按钮,就能把几百个文件下载下来,并且按照分类分好文件夹和压缩。
痛点:
一次发送几百个请求到服务器,服务器是不能处理的,从而导致连接被取消。不同浏览器的同一域名下的最大请求并发数量是有限的,那下载文件的时间一长,后面的接口就会超时断开连接。所以,我们需要异步去处理这些请求。
实现思路
首先,我们实现一个异步下载文件的方法,一次只有10个并发请求。那我们的思路就是,把文件对象放数组,一开始取10个对象,请求文件链接下载文件,一个请求完了,就开始请求第11个对象文件,如此直到数组为空。
const DownloadMultiFile = (list, opts) => {
let fileList = [...list];
let promiseFile = [];
let max = 10;
return new Promise((resolve) => {
const downFile = () => {
if (fileList.length) {
const url = fileList.shift().url;
const axiosFile = fetch(url, opts)
promiseFile.push(axiosFile);
// 一个文件处理完就再把文件推进来处理
axiosFile.then(() => {
downFile();
}).catch(() => {
downFile();
})
} else {
// 所有的请求都有了结果以后,就可以resolve了,一次resolve之后,再调用也不会改变DownloadMultiFile的结果
Promise.allSettled(promiseFile).then(result => resolve(result));
}
}
// 如果文件没有10个,就直接把文件一通下载
const count = Math.min(fileList.length, max)
// 前10个文件不需要异步
for (let i = 0; i < count; i++) {
downFile();
}
})
}
到这里,很基础的文件下载就完成了。
如果文件比较机密,访问文件路径需要在请求链接拼上授权信息,那我们就需要在访问文件前先调用授权接口获取授权信息,并且缓存下来,在每次访问文件时都需要先看看授权信息过期了没,如果过期了需要重新获取。
授权的请求需要考虑同一时间被重复调用的问题。因为一开始是有10个请求的并发。
可以用一个字段判断接口是否在调用,调用前设为true,调用结束设为false,为true的时候,不允许再调用接口,但是这个比较没有美感,也不通用。可以利用了缓存promise结果的方式保证只调用一次接口。这是参考的kezy写的# typescript - promise缓存与复用
const authAction = () => {
let result = null;
let auth = null;
let i = 0;
const cache = (promise) => {
console.log("cache", result)
return result = result || promise().catch((err) => {
result = null;
return Promise.reject(err)
})
}
const queryAuth = () => {
return new Promise(resolve => {
console.log("fetch")
// auth = fetch()
setTimeout(() => {
const val = {
value: i++,
expiresTime: new Date().getTime() + 6000
}
resolve(val);
}, 2000)
})
}
return {
getAuth: () => {
// 授权过期要把缓存的promise清掉重新请求接口,接口的结果也要清空,防止后面并发时会把result清空导致缓存promise失效
if (auth && auth.expiresTime < new Date().getTime()) {
result = null;
auth = null;
}
const queryResult = cache(queryAuth);
queryResult.then(result => {
auth = result;
})
return queryResult;
}
}
}
到这里,异步下载文件已经完成了。最后就是把文件放文件夹并压缩保存了。这里我使用的是jszip插件,具体用法我就不讲了,大家可以自己去官网查看api。