如何绕开浏览器批量下载的限制

前言

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

触发单个下载

在浏览器中触发下载,我们可以借用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个文件。

相关推荐
默_笙4 分钟前
🚋 从流水线到地铁网:为什么复杂 AI 都要拆成多 Agent(上)——LangGraph 基础入门
前端·javascript
科技苑19 分钟前
前后端分离与微服务架构如何协同?
前端·后端·前端框架
神秘的猪头32 分钟前
TypeScript 高级用法全解析:从泛型到 infer,把类型系统真正用起来
前端·typescript
moMo34 分钟前
React Hooks 与闭包
前端·react.js
柚yuzumi42 分钟前
彻底搞懂 JavaScript 类型转换:显式转换、隐式转换与 ToPrimitive
前端·javascript·node.js
向北丶43 分钟前
vscode 导入语句排序和删除未使用的导入
前端·visual studio code
PHP实战开发录1 小时前
PHP接口偶发变慢怎么查
前端·php·开发
向北丶1 小时前
vite项目中配置alias别名
前端·vite
COOLMO研究AI2 小时前
Next.js App Router 中 sitemap、robots 与 canonical 的配置与排查
前端·web·js·网站优化
DyLatte2 小时前
AI 给了我 8 个优化方案,全都是对的,但没有一个有用
前端·后端·程序员