可通过FormData配合XHR实现多文件并发上传并监听单文件进度,需为每个文件创建独立FormData实例、限制并发数、绑定xhr.upload.onprogress事件,并用Promise.allSettled聚合结果。

可以通过 FormData 配合 XMLHttpRequest(或 Fetch + ReadableStream)实现多文件并发上传,并利用 XMLHttpRequest.upload.onprogress 监听单个请求的上传进度;但需注意:原生 FormData 本身不提供进度控制能力,真正起作用的是 XHR 的上传事件和并发管理逻辑。
构建带文件的 FormData 并发队列
先遍历文件列表,为每个文件创建独立的 FormData 实例(避免多个文件混在一个请求里导致进度不可分),再用 Promise.all 或自定义并发控制器发起多个 XHR 请求:
- 每个文件单独封装成一个 FormData:
formData.append('file', file, file.name),可额外加字段如formData.append('uploadId', uuid()) - 限制并发数(例如最多 3 个同时上传),避免浏览器连接耗尽或服务端压力过大,可用简单计数器或基于 Promise 的并发池
- 不要把所有文件塞进同一个 FormData —— 否则只能拿到整体进度,无法区分各文件状态
用 XMLHttpRequest 绑定单文件上传进度
XHR 是目前最稳定支持细粒度上传进度的方案(Fetch 原生不提供上传进度,需借助 Streams + Blob.slice 模拟,复杂且兼容性差):
- 创建 XHR 实例后,立即监听
xhr.upload.onprogress = e => { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100 } } - 确保服务端返回 CORS 头
Access-Control-Allow-Headers: *,否则部分浏览器会屏蔽 upload 事件 - 将进度回调与对应文件 ID 关联(如用闭包、Map 存储 file.uid → progressCallback),防止异步执行错乱
汇总进度与错误处理
上传是“并发但独立”的过程,需维护全局状态来反映整体进展:
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
- 用 Map 或数组记录每个文件的当前进度(0–100)、状态(uploading / success / error)
- 通过
Promise.allSettled()聚合全部请求结果,避免单个失败中断整体;对 rejected 结果提取 status/text 等做友好提示 - 服务端建议返回统一 JSON 格式,如
{ code: 0, data: { fileId: 'xxx', url: 'https://...' } },前端据此更新 UI
简易并发上传函数示例(含进度回调)
以下为可直接运行的核心逻辑片段(省略 UI 更新):
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();
<p>const uploadOne = ({ file, uid }) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file, file.name);</p><pre class="brush:php;toolbar:false;"> 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 onError?.({ uid, msg: 'Network error' }) && reject(new Error('Network error'));
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
};
// 并发控制:每次最多 maxConcurrent 个 const uploadBatch = async (batch) => Promise.all(batch.map(uploadOne)); const batches = []; for (let i = 0; i
return Promise.all(batches.map(uploadBatch)).then(() => 'All done'); }
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










