约 6 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
HTTP 404、500、502 都是「请求成功到达服务器并得到了响应」,fetch 会正常走 then。想让业务代码拿到错误,必须自己检查 res.ok 或 res.status。这就是「不报错却拿不到数据」的第一大原因。
一个 if 就能把静默失败变成显式异常,让 catch 真正接得住错误。
async function getJSON(url, options) {
const res = await fetch(url, options);
// 4xx / 5xx 不会 reject,必须手动抛错
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
return res.json();
}
getJSON('/api/user/1')
.then(render)
.catch(err => console.error(err.message)); // HTTP 404 Not Found「拿不到数据」的表现千差万别,先在表里找到你的现象,再按一行修复处理。
| 现象 | 常见原因 | 一行修复 |
|---|---|---|
| res.ok 为 false | URL 拼错、ID 不存在、权限不足 | 打印 res.status 与实际请求的完整 URL 对照 |
| res.json() 抛 Unexpected token | 接口返回了 HTML 404 页或空响应体 | 先用 res.text() 打印原始响应,再决定怎么解析 |
| then 里 data 是 undefined | 响应结构和你假设的不一样 | console.log 整个 data,确认字段路径 |
| 请求 pending 永远不返回 | 服务端挂起、代理没有超时 | 用 AbortController 设超时主动中止 |
| CORS 报错且 status 为 0 | 跨域被浏览器拦截 | 检查响应头 Access-Control-Allow-Origin |
res.json() 内部会调用 JSON.parse,只要响应体不是合法 JSON(常见是登录跳转的 HTML、错误页、空字符串)就直接抛 SyntaxError。稳妥做法是先取 text,再尝试 parse,失败时把前 200 个字符带进错误信息里。
async function safeJSON(res) {
const text = await res.text();
try {
return JSON.parse(text);
} catch (e) {
// 把响应片段带进错误里,方便定位是哪种页面
throw new Error(
`不是合法 JSON(HTTP ${res.status}): ${text.slice(0, 200)}`
);
}
}按顺序做,基本 5 分钟内能定位问题在哪一层。
async function fetchWithTimeout(url, ms = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} finally {
clearTimeout(timer); // 无论成败都要清掉定时器
}
}
// 被中止时抛出名为 "AbortError" 的 DOMException
fetchWithTimeout('/api/slow').catch(err => {
if (err.name === 'AbortError') console.error('请求超时');
else console.error(err.message);
});只有「值得重试」的失败才该重试,否则只会把故障放大。
class RetryableError extends Error {}
async function fetchRetry(url, { retries = 3, backoff = 1000 } = {}) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return res.json();
// 4xx 不值得重试,直接抛普通错误
if (res.status < 500) throw new Error(`HTTP ${res.status}`);
throw new RetryableError(`HTTP ${res.status}`);
} catch (err) {
const isLast = i === retries - 1;
if (isLast || !(err instanceof RetryableError)) throw err;
// 1s → 2s → 4s,加随机抖动防止同时重试
const delay = backoff * 2 ** i + Math.random() * 300;
await new Promise(r => setTimeout(r, delay));
}
}
}99% 的「不报错拿不到数据」只要一个 if (!res.ok) throw new Error(res.status) 就能暴露出来。需要核对状态码语义时,用 HTTP 状态码查询工具确认 4xx/5xx 的准确含义,再把响应 JSON 粘进格式化工具看字段层级。
不会。fetch 只在网络失败、请求被中止时 reject,404/500 属于正常响应,必须自己检查 res.ok 或 res.status 并手动抛错。
响应体不是合法 JSON,常见是 404 HTML 页、登录跳转页或空字符串。先 await res.text() 打印前几百个字符确认内容再解析。
用 AbortController 配合 setTimeout,超时后调用 controller.abort(),fetch 会抛出 name 为 AbortError 的 DOMException。新浏览器也可直接用 AbortSignal.timeout(5000)。
网络错误与 5xx 适合配合指数退避重试;4xx 是请求本身的问题,重试无意义,应直接暴露给业务层处理。