
























你是不是也遇到过这种情况:后端小哥信誓旦旦说接口没毛病,你自己用Postman试了也正常,可前端就是拿不到数据。
控制台一看,报的错牛头不对马嘴,什么“Failed to fetch”…… 更气人的是,明明写了 .catch(),错误却没抓住,眼睁睁看着页面白屏。
记得我刚开始写前端那会儿,一听原生的 fetch 比 XMLHttpRequest 好用,立马把项目里的 axios 全换了。结果项目一运行,报错信息差点没把我晃晕。
最核心的一个坑,也是90%新手会掉的——fetch 只把网络错误算作 reject,HTTP错误状态(比如404、500)它照样当成功处理。
换句话说,哪怕服务器返回404,fetch的Promise仍然会走.then(),你要是不手动判断,后面一堆代码就拿着 undefined 开心地执行下去了。
这篇文章不跟你背文档,咱就像朋友聊天一样,把我这些年对 fetch 的理解、踩过的坑、还有现在一直用的“稳妥写法”,全倒给你。
没错,我觉得 fetch 特别像点外卖。你下单(发请求),平台给你个订单号(Promise),外卖小哥会不会送到你家呢?不一定,可能商家自己取消订单了,但平台会告诉你“订单已生成”。
fetch 的 Response 对象就是这个“订单详情”。你得自己打开看看里面的 status (200?404?)和 ok 字段,不然外卖没送到你还给人打五星好评呢。
咱们不废话,我先给你看一段可以直接复制的异步请求封装,然后再拆开说里头的门道。
async function request(url, options = {}) {
try {
const response = await fetch(url, options);
// 重点1:先判断HTTP状态码
if (!response.ok) {
// 顺手把服务器返回的错误信息抛出去
const errorBody = await response.text();
throw new Error(`请求失败 ${response.status}: ${errorBody}`);
}
// 重点2:根据预期类型解析
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return await response.json();
}
return await response.text();
} catch (error) {
// 重点3:在这儿统一处理上报和提示
console.error("请求出错:", error);
throw error; // 继续抛给调用方,别默默吞掉
}
}
官方给了个现成属性 response.ok ,相当于 status 在 200-299 之间。千万别自己手写 if (status === 200) ,我早期吃过亏,碰到 201 Created 这种成功状态就直接卡死了。
你可能会问,为啥上面我先把错误信息用 text() 读了,外面还能正常 json() ?因为我在抛错误前就已经消费掉了。
这引出 fetch 最容易翻车的点——Response 的 .json()、.text()、.blob() 都只能调一次。想既看原始文本又当 JSON 用?不行!要么先 clone() 一份,要么像我这样,只在确定是错误时才读一次。
再说个真实案例:有次我调试接口,想在控制台打印一下原始返回值,于是提前用 await response.json() 把数据拿出来了,接着返回给业务代码再用 .json() 一次,结果直接报错。调试了半天才发现是因为 body 流已经被读完了。
经常有人问我:为啥后端明明返回了JSON,我的代码还是报错?
很多情况是后端在返回 401 或 403 时,虽然状态码不对,但 body 还是 JSON 格式的错误信息。如果你不先用 text() 看一眼直接 json(),很可能因为 content-type 不匹配而解析失败,结果连真正的错误都看不到。
我的习惯是,在非 ok 分支里一律先用 text() 拿到原始内容,既安全,又能把服务器给的提示原样显示出来,用户体验好不少。
是不是以为写成 async/await 就万事大吉了?我可见过太多同学在 useEffect 里直接 async,或者 forEach 里用 await,结果数据像没睡醒一样迟迟不来。
如果你在循环里发请求,老老实实用 for...of 配合 await,或者用 Promise.all 并行处理。不然你可能会度过一个非常崩溃的调试下午。
拿我最开始翻车的代码举例吧,当时想批量拉取用户详情,写法大概长这样:
// ❌ 错误示范:forEach里用await,毛用没有
const userIds = [1, 2, 3];
const users = [];
userIds.forEach(async (id) => {
const res = await fetch(`/api/user/${id}`);
const data = await res.json();
users.push(data);
});
// 这时候users大概率还是个空数组,因为forEach不等await
console.log(users); // 惊不惊喜?意不意外?
以为这样就能一个个按顺序拿到数据,但结果,天真了。forEach 的回调被标记为 async 后,并不会让外层的同步代码等它执行完,相当于你安排了三个人同时出门办事,还没等他们回来你就直接宣布“任务全部完成”了。
后来学乖了,改成了两种稳妥写法:
// ✅ 按顺序一个个来(适合有依赖的情况)
for (const id of userIds) {
const res = await fetch(`/api/user/${id}`);
const data = await res.json();
users.push(data);
}
// ✅ 或者不关心顺序,全扔出去并行跑
const promises = userIds.map(id =>
fetch(`/api/user/${id}`).then(res => res.json())
);
const users = await Promise.all(promises);
记住了,只要在循环里用 await,老老实实放弃 forEach,拥抱 for...of,或者直接用 map + Promise.all。这个坑我替你踩过了,别再往里掉啦!!!
好啦,今天掏心窝子的分享就到这儿。如果你在 fetch 上还躺过其他奇奇怪怪的坑,或者对我的封装有啥改进建议,特别欢迎给我留言。要是觉得这篇唠嗑能帮你省几根头发,动动手指点个“赞”,别让它在收藏夹吃灰呀~ 🌟
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。