h5上传应采用队列+计数器控制并发(阈值3–5),每个文件独立formdata和请求,用xhr监听upload.onprogress获取进度,任务对象含iscancelled支持取消,避免promise.all和内存泄漏。

在 H5 环境中,浏览器本身不限制并发上传数量,但盲目并发会迅速耗尽连接池、触发超时、拖慢页面甚至导致内存泄漏。真正有效的做法是:自己建队列 + 控制活跃请求数 + 每个文件独立处理。
用队列 + 计数器控制并发
核心逻辑很简单:维护一个待上传文件队列和一个当前正在上传的数量计数器。每次准备发起新请求前,先检查计数器是否低于阈值(推荐 3–5);只有空闲才取下一个文件执行上传。
- 定义最大并发数,比如 MAX_CONCURRENT = 4
- 把用户选中的
FileList转为数组:const files = Array.from(input.files) - 每个文件包装成任务对象,含
id、file、status、progress等字段,推入队列 - 写一个
processQueue()函数,循环检查“队列非空且计数未满”,满足则取出一个任务执行上传,并在完成回调里递归调用自身
每个文件单独发请求,别塞进同一个 FormData
把多个文件 append 到同一个 FormData 实例再发一次请求,看似省事,实则无法区分进度、失败定位困难、重试成本高。正确方式是:每个文件创建独立的 FormData,并单独发起 XMLHttpRequest 或 fetch 请求。
-
formData.append('file', file, file.name)—— 注意第三个参数传原始文件名,利于后端保存 - 如需携带业务参数(如 user_id、category),可统一 append 进每个 FormData,无需额外拼接
- 避免使用
Promise.all同时启动全部请求,它一旦某个失败就整批中断,也不支持单文件重试
绑定上传进度并管理状态
XMLHttpRequest 是目前最稳定支持细粒度上传进度的方案。Fetch 原生不提供上传进度事件,模拟成本高、兼容性差。
- 创建 XHR 实例后,立即监听
xhr.upload.onprogress,通过e.loaded / e.total计算百分比 - 用闭包或 Map 把进度回调与对应文件 ID 绑定,防止异步执行错乱
- 用
onloadend(不是 onload)作为完成信号,统一更新任务状态为completed或error - 上传完成后主动释放对
file的引用,尤其避免长期存于闭包或 React state 中,防止内存堆积
支持取消与中断未发出的任务
用户点击“取消上传”时,已开始上传的请求可用 xhr.abort() 中断;但排队中还没发出去的任务,必须靠逻辑标记来跳过。
- 给每个任务对象加一个
isCancelled标志位 - 在真正创建 XHR 前检查该标志,若为 true 就直接跳过
- 取消操作只需遍历队列,把所有 pending 和 uploading 状态的任务设为已取消
- 注意:XHR 不支持 AbortController,如需统一信号管理,建议切换到 fetch +
{ signal }方案











