前言
最近遇到一个需求,需要将批量选择的图片,批量一个个下载。
触发单个下载
在浏览器中触发下载,我们可以借用a元素来触发。
ts
const downloadFile = async (url: string, name: string) => {
const res = await fetch(url);
const blob = await res.blob();
const strList = url.split('.');
const type = strList[strList.length - 1];
const downUrl = window.URL.createObjectURL(blob);
const downloadElement = document.createElement('a');
document.body.appendChild(downloadElement);
downloadElement.href = downUrl;
downloadElement.download = `${name}.${type}`;
downloadElement.click();
document.body.removeChild(downloadElement); // 下载完成移除元素
window.URL.revokeObjectURL(downUrl);
};
批量触发
单个实现了,批量就promise来批量执行就好了
ts
export const batchDownloadFile = async (files: { url: string; name: string; }[], showLoading = true) => {
if (showLoading) {
// 全局loading,可以忽略
loading.value = ElLoading.service({
lock: true,
text: '正在下载中...',
background: 'rgba(0, 0, 0, 0.7)',
});
}
const promiseList = files.map((file) => {
return new Promise(async (resolve, reject) => {
try {
downloadFile(file.url, file.name)
resolve(null);
} catch {
reject('下载错误');
}
});
});
const result = await Promise.allSettled(promiseList);
if (showLoading) {
loading.value?.close();
}
// 返回结果
return result.map((item, index) => {
return {
isSuccess: item.status === 'fulfilled',
name: files[index].name,
url: files[index].url,
};
});
};
使用allSettled而不用all是因为,Promise.all是有一个被拒绝就会被拒绝,不符合部分成功的情况,所以这里使用的是allSettled。
浏览器限制
对于一次性(即一次宏任务的event loop)触发下载,浏览器会限制一次触发a元素下载最多10次。 对于下载选择的图片超过10张的情况下,要使用分批来下载。
使用setTimeout来绕过限制
既然一次宏任务限制10个,那么就借用setTimeout来分批下载即可。
ts
export const batchDownloadFile = async (files: { url: string; name: string; }[], showLoading = true) => {
const batchSize = 10;
const delay = 1000;
if (showLoading) {
loading.value = ElLoading.service({
lock: true,
text: '正在下载中...',
background: 'rgba(0, 0, 0, 0.7)',
});
}
const result: { isSuccess: boolean; name: string; url: string; }[] = [];
const downloadBatch = async (batch: { url: string; name: string; }[]) => {
const promiseList = batch.map((file) => {
return new Promise(async (resolve, reject) => {
try {
await downloadFile(file.url, file.name);
resolve(null);
} catch {
reject('下载错误');
}
});
});
const batchResult = await Promise.allSettled(promiseList);
batchResult.forEach((item, index) => {
result.push({
isSuccess: item.status === 'fulfilled',
name: batch[index].name,
url: batch[index].url,
});
});
};
const delayPromise = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms); });
// 这里使用递归去执行分批下载
const processBatches = async (remainingFiles: { url: string; name: string; }[]) => {
if (remainingFiles.length === 0) {
return;
}
const batch = remainingFiles.slice(0, batchSize);
const remaining = remainingFiles.slice(batchSize);
await downloadBatch(batch);
await delayPromise(delay);
await processBatches(remaining);
};
await processBatches(files);
if (showLoading) {
loading.value?.close();
}
return result;
};
改动后,顺利实现了批量下载超过10个文件。