Skip to content

Commit

Permalink
feat: 新增文章
Browse files Browse the repository at this point in the history
  • Loading branch information
Lstmxx committed Oct 18, 2024
1 parent ae451b6 commit 01a00b5
Showing 1 changed file with 148 additions and 0 deletions.
148 changes: 148 additions & 0 deletions source/_posts/2024-10-18-如何绕开浏览器批量下载的限制.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,151 @@ date: 2024-10-18 10:53:09
tags:
categories: typescript 浏览器
---

## 前言

最近遇到一个需求,需要将批量选择的图片,批量一个个下载。

## 触发单个下载

在浏览器中触发下载,我们可以借用 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 个文件。
![[Pasted image 20241017174747.png]]

0 comments on commit 01a00b5

Please sign in to comment.