Promise.all() 无原子性但可实现“全成或全败”语义,需手动检查 fetch.response.ok 并抛错;配合 AbortController 支持超时与取消;服务端须幂等+事务兜底。
Promise.all() 本身没有原子性,但能实现“全成或全败”的语义效果——只要任一 fetch 失败(包括网络错误、4xx/5xx 响应、超时等),整个上传就中止并拒绝,不执行后续逻辑。关键在于正确处理 fetch 的响应状态和错误边界,不能只依赖 Promise.all() 的默认行为。
fetch 在网络失败(如断网、DNS 失败)时会 reject,但对 400、500 等 HTTP 错误响应仍 resolve。若不手动检查 response.ok,Promise.all() 会把失败响应当作“成功”纳入结果数组,破坏“全败”逻辑。
✅ 正确做法:每个 fetch 后链式调用 .then() 检查 status,并主动 throw 错误:
const uploadFile = (file, url) => { const formData = new FormData(); formData.append('file', file); return fetch(url, { method: 'POST', body: formData, }) .then(response => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); // 或其他期望的解析 });};
使用 Promise.all() 包裹所有 uploadFile 调用,一旦任意一个抛出错误(包括网络异常、HTTP 错误、JSON 解析失败),整个 Promise.all() 立即 reject,其余仍在进行中的请求不会被取消(浏览器层面无法强制 abort 已发出的 fetch),但业务逻辑可立即停止后续操作(如 UI 提示、清理状态)。
⚠️ 注意:Promise.all() 不取消请求,仅控制逻辑流。如需真正中断上传,需配合 AbortController(见下一点)。
✅ 示例调用:
const files = [file1, file2, file3];const uploadPromises = files.map(f => uploadFile(f, '/api/upload'));Promise.all(uploadPromises) .then(results => { console.log('全部上传成功', results); // ✅ 执行“全成”后置逻辑:跳转、刷新列表、提示成功 }) .catch(err => { console.error('任一上传失败,整体视为失败', err); // ❌ 执行“全败”回滚逻辑:提示用户、保留原始文件状态、不提交表单 });
原生 fetch 不支持超时,长时间卡住的请求会让 Promise.all() 无限等待。可用 AbortController 实现超时控制,并在用户主动取消时中止所有请求。
✅ 改进 uploadFile 支持 signal 和 timeout:
const uploadFile = (file, url, { timeout = 30000 } = {}) => { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const formData = new FormData(); formData.append('file', file); return fetch(url, { method: 'POST', body: formData, signal: controller.signal, }) .then(response => { clearTimeout(id); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); }) .catch(err => { clearTimeout(id); if (err.name === 'AbortError') throw new Error('上传超时或已取消'); throw err; });};
前端“全成或全败”只是客户端语义。服务端必须保证:单个文件上传接口幂等(相同文件 ID 多次提交不重复存)、且批量上传应走原子事务(如数据库写入+OSS 上传绑定在同一事务中)。否则前端重试或并发可能引发数据不一致。
✅ 建议服务端设计: