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

前言

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

触发单个下载

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

相关推荐
学不会•1 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder6 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727576 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架