应为每个文件单独创建 formdata 实例并传入文件名,用 xhr 控制并发(如 activecount

多个文件不能共用一个 FormData 实例
把所有 File 对象塞进同一个 FormData,会导致上传进度不可拆分、失败定位困难、重试逻辑复杂。浏览器和服务器都把它当做一个整体请求处理,xhr.upload.onprogress 只能报告总字节数,无法知道哪个文件卡在 20%。
正确做法是为每个文件单独创建 FormData 实例:
-
formData.append('file', file, file.name)—— 必须传第三个参数(文件名),否则服务端可能收不到原始文件名 - 可额外加字段如
formData.append('uploadId', 'uuid-123'),方便前后端日志对齐 - 避免使用
formData.append('files', fileList)这类写法,FileList不会被自动展开为多个条目
并发控制必须由前端主动实现
FormData 本身不提供并发限制能力,它只是数据容器。真正决定“同时发几个请求”的是你的 XHR/Fetch 调度逻辑。不限制并发数容易触发浏览器连接池耗尽(Chrome 默认同域最多 6 个 TCP 连接),或让服务端短时间承受大量 multipart 解析压力。
推荐用轻量级并发池而非 Promise.all:
- 用计数器 + 队列:维护
activeCount ,每次成功/失败后 <code>resolve下一个待上传任务 - 不要依赖
fetch的默认行为 —— 它不暴露上传进度,且并发策略由浏览器底层控制,不可靠 - XHR 是目前唯一稳定支持
upload.onprogress的方案,别为了“语法简洁”换成 fetch 后再自己 slice Blob 模拟进度,兼容性和性能都差
字段命名必须与后端约定一致,且区分普通字段与文件字段
常见错误是把文本字段和文件字段混在同一个 key 下,比如都用 formData.append('data', value)。后端框架(如 Express + multer、SpringMVC)通常按字段类型分别解析:text/plain 字段走 body parser,multipart/form-data 中的文件字段走 file parser。
结构设计上建议明确分离:
- 文件类字段统一用复数名 + 明确语义,如
formData.append('images', blob, 'logo.png') - 元数据类字段用前缀,如
formData.append('meta.title', 'xxx')或formData.append('user_id', '123') - 避免 key 冲突:如果后端期望
file字段接收单个文件,就不要在同个请求里 append 多次file,否则部分框架只取第一个
服务端返回必须可被前端状态机消费
高并发上传不是“发完就完”,而是要聚合多个独立请求的结果并驱动 UI 更新。这意味着服务端响应格式必须稳定、可预测,不能依赖 HTTP 状态码做业务判断(比如 200 但内容是 {error: "quota_exceeded"})。
关键点:
- 统一返回 JSON,包含
code、message、data(含fileId/url等) - 前端用
Promise.allSettled()接收全部结果,而不是Promise.all()—— 后者遇到任一 reject 就中断整个数组 - 每个请求的
uploadId必须透传到响应体中,否则无法将“第 3 个请求成功”映射回 UI 上具体哪张图
最易被忽略的是:上传中途页面刷新或用户关闭标签页,XMLHttpRequest 会静默终止,且无回调。真要健壮,得配合 IndexedDB 缓存未完成任务 ID,并在下次加载时检查服务端状态。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











