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

前言

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

触发单个下载

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

相关推荐
踩着两条虫2 分钟前
AI 驱动的 Vue3 应用开发平台 深入探究(六):双向代码转换之DSL到Vue代码生成
前端·vue.js·ai编程
Wect2 分钟前
React 中的双缓存 Fiber 树机制
前端·react.js·面试
天才熊猫君2 分钟前
Vue 3 中 Watch 的陷阱:为什么异步操作后创建的监听会泄漏?
前端·javascript
梵得儿SHI3 分钟前
Vue3 生态工具实战进阶:API 请求封装 + 样式解决方案全攻略(Axios/Sass/CSS Modules)
前端·css·vue3·sass·api请求·样式解决方案·组合式api管理
有梦想的咸鱼还是咸鱼吗4 分钟前
前端必会|防抖与节流从原理到实战,解决90%高频事件卡顿问题
前端
阿诺木6 分钟前
Node.js 局域网设备发现:mDNS、UDP 广播和子网扫描
前端
盐焗乳鸽还要砂锅7 分钟前
亲手造一只有灵魂的 AI 小龙虾是种什么体验?
前端·llm·agent
YimWu8 分钟前
Opencode 核心设计-Session会话机制
前端·agent·ai编程
Mintopia11 分钟前
诗词如何影响人:从认知机制到可落地的文本分析技术路线
前端·代码规范
WaywardOne17 分钟前
iOS必看!Deepseek给的Runtime实现原理,通俗易懂~
前端·面试