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

前言

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

触发单个下载

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

相关推荐
前端大卫23 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘39 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare40 分钟前
浅浅看一下设计模式
前端
Lee川43 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端