可通过FormData配合XHR实现多文件并发上传并监听单文件进度,需为每个文件创建独立FormData实例、限制并发数、绑定xhr.upload.onprogress事件,并用Promise.allSettled聚合结果。
可以通过 FormData 配合 XMLHttpRequest(或 Fetch + ReadableStream)实现多文件并发上传,并利用
监听单个请求的上传进度;但需注意:原生 FormData 本身不提供进度控制能力,真正起作用的是 XHR 的上传事件和并发管理逻辑。
构建带文件的 FormData 并发队列
先遍历文件列表,为每个文件创建独立的 FormData 实例(避免多个文件混在一个请求里导致进度不可分),再用 Promise.all 或自定义并发控制器发起多个 XHR 请求:
每个文件单独封装成一个 FormData:
,可额外加字段如
限制并发数(例如最多 3 个同时上传),避免浏览器连接耗尽或服务端压力过大,可用简单计数器或基于 Promise 的并发池
不要把所有文件塞进同一个 FormData —— 否则只能拿到整体进度,无法区分各文件状态
用 XMLHttpRequest 绑定单文件上传进度
XHR 是目前最稳定支持细粒度上传进度的方案(Fetch 原生不提供上传进度,需借助 Streams + Blob.slice 模拟,复杂且兼容性差):
创建 XHR 实例后,立即监听
确保服务端返回 CORS 头
,否则部分浏览器会屏蔽 upload 事件
将进度回调与对应文件 ID 关联(如用闭包、Map 存储 file.uid → progressCallback),防止异步执行错乱
汇总进度与错误处理
上传是“并发但独立”的过程,需维护全局状态来反映整体进展:
使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
下载
立即学习
“
前端免费学习笔记(深入)
”;
用 Map 或数组记录每个文件的当前进度(0–100)、状态(uploading / success / error)
通过
聚合全部请求结果,避免单个失败中断整体;对 rejected 结果提取 status/text 等做友好提示
服务端建议返回统一 JSON 格式,如
,前端据此更新 UI
简易并发上传函数示例(含进度回调)
以下为可直接运行的核心逻辑片段(省略 UI 更新):
};// 并发控制:每次最多 maxConcurrent 个
const uploadBatch = async (batch) => Promise.all(batch.map(uploadOne));
const batches = [];
for (let i = 0; i < fileEntries.length; i += maxConcurrent) {
batches.push(fileEntries.slice(i, i + maxConcurrent));
}return Promise.all(batches.map(uploadBatch)).then(() => 'All done');
}
XMLHttpRequest.upload.onprogressformData.append('file', file, file.name)formData.append('uploadId', uuid())xhr.upload.onprogress = e => { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100 } }Access-Control-Allow-Headers: *Promise.allSettled(){ code: 0, data: { fileId: 'xxx', url: 'https://...' } }
function uploadFiles(files, options = {}) {
const { maxConcurrent = 3, onProgress, onSuccess, onError } = options;
const fileEntries = Array.from(files).map(file => ({ file, uid: Math.random().toString(36).substr(2, 9) }));
const results = new Map();
const uploadOne = ({ file, uid }) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file, file.name);
xhr.upload.onprogress = e => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
onProgress?.({ uid, percent, total: e.total, loaded: e.loaded });
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const res = JSON.parse(xhr.responseText);
onSuccess?.({ uid, data: res.data });
resolve(res);
} catch {
onError?.({ uid, msg: 'Invalid JSON response' });
reject(new Error('Invalid JSON'));
}
} else {
onError?.({ uid, status: xhr.status, statusText: xhr.statusText });
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => onError?.({ uid, msg: 'Network error' }) && reject(new Error('Network error'));
xhr.open('POST', '/api/upload');
xhr.send(formData);
});