web学习笔记编程技术学习与分享
首页 / 前端开发 / 正文

Fetch 与 Promise 的错误处理陷阱

2026 年 07 月 19 日约 3 分钟阅读 前端开发

fetch 有一个反直觉的设计:只要服务器返回了响应,Promise 就是 resolved,哪怕状态码是 500。它只在网络层失败(断网、DNS 错误、CORS 被拒)时 reject。

必须手动检查 ok

const res = await fetch('/api/user');
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();

忘记这一步的后果是:接口 500 返回一段 HTML 错误页,res.json() 抛出语法错误,日志里看到的是一句莫名其妙的 JSON 解析失败。

没有内置超时

fetch 本身不支持 timeout,需要用 AbortController:

const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 8000);
try {
  const res = await fetch(url, { signal: ctrl.signal });
  return await res.json();
} finally {
  clearTimeout(timer);
}

取消时抛出的是 AbortError,要和业务错误区分开,不要一律提示「网络异常」。

错误处理的层次

一个健壮的请求封装通常分三层:

  1. 传输层:网络失败、超时、取消;
  2. 协议层:状态码非 2xx、响应不是合法 JSON;
  3. 业务层:{ code: 40001, msg: '...' } 这类业务错误。

三层混在一起处理,会导致「用户取消请求」也弹一个错误提示。

async function request(url, opt = {}) {
  const res = await fetch(url, opt);
  if (!res.ok) throw new HttpError(res.status);
  const data = await res.json();
  if (data && data.code && data.code !== 0) throw new BizError(data.code, data.msg);
  return data;
}

并发请求

Promise.all 有一个失败就整体失败,适合「缺一不可」的场景。需要部分成功时用 Promise.allSettled:

const results = await Promise.allSettled(tasks);
const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value);

另外提醒一句:Promise.all 并不会取消其他请求,失败后它们仍在后台跑,只是结果被忽略。涉及写操作时要考虑幂等。

JavaScriptFetch异步