Fetch 与 Promise 的错误处理陷阱
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,要和业务错误区分开,不要一律提示「网络异常」。
错误处理的层次
一个健壮的请求封装通常分三层:
- 传输层:网络失败、超时、取消;
- 协议层:状态码非 2xx、响应不是合法 JSON;
- 业务层:
{ 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 并不会取消其他请求,失败后它们仍在后台跑,只是结果被忽略。涉及写操作时要考虑幂等。