一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何利用 Promise.all() 的原子性配合 fetch 实现多文件同时上传的全成或全败逻辑

时间:2026-07-20 11:00:59 编辑:袖梨 来源:一聚教程网

Promise.all() 无原子性但可实现“全成或全败”语义,需手动检查 fetch.response.ok 并抛错;配合 AbortController 支持超时与取消;服务端须幂等+事务兜底。

Promise.all() 本身没有原子性,但能实现“全成或全败”的语义效果——只要任一 fetch 失败(包括网络错误、4xx/5xx 响应、超时等),整个上传就中止并拒绝,不执行后续逻辑。关键在于正确处理 fetch 的响应状态和错误边界,不能只依赖 Promise.all() 的默认行为。

fetch 默认不拒绝 HTTP 错误状态

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);    // ❌ 执行“全败”回滚逻辑:提示用户、保留原始文件状态、不提交表单  });

增强健壮性:添加超时与请求取消(AbortController)

原生 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 上传绑定在同一事务中)。否则前端重试或并发可能引发数据不一致。

✅ 建议服务端设计:

  • 上传前先申请 batch_id,所有文件携带该 ID;
  • 后端收到全部文件后,再统一落库 + 写对象存储;
  • 任一环节失败,回滚整个 batch,返回 500 或自定义错误码;
  • 前端捕获该错误,触发全局失败流程。

热门栏目