AJAX请求失败怎么办 新手遇到的跨域超时和参数传递问题怎么解决 从浏览器网络面板看到请求卡在pending状态说起
那个让你抓狂的”Pending”
说实话,我刚接触前端那会儿,每次看到网络面板里请求卡在pending状态不动,心里就咯噔一下,仿佛自己的代码被什么神秘力量吞噬了。那种感觉就像你把信投进邮筒,等了半小时、一小时,邮递员既没来也没走,你就只能盯着邮筒发呆。
今天咱们就掰开揉碎了聊这件事,从你看到pending状态的那一刻说起,一路把跨域、超时、参数传递这些坑都填平。
先看网络面板,别急着猜
你在浏览器里按F12,打开开发者工具,切到Network(网络)标签,刷新页面或者触发你的请求,然后你会看到类似这样的画面:
请求URL: http://api.example.com/users
状态: pending...
进度: --
pending不是错误,它只是一种状态——意味着浏览器已经把请求发出去了,但服务器还没有返回任何响应。问题可能出在发送环节、传输环节、或者接收环节,你得用排除法一个个查。
先做的第一件事:看请求是否真的发出去了。
有些新手写代码,请求根本没触发,就以为卡在pending。比如:
// 这种写法是错的,addEventListener的回调没有正确绑定
btn.onclick = getFormData(); // 括号多了,页面加载就立刻执行了,而不是点击时
// 正确的写法
btn.onclick = function() {
getFormData();
};
// 或者
btn.addEventListener('click', getFormData);
如果你发现网络面板里压根没有这个请求,那问题不在pending,而在触发环节。先去检查事件绑定有没有写对。
请求发了,但就是没响应——排查步骤
第一步:看Status列有没有变化
如果状态一直显示(pending),说明浏览器收到了响应头但还没完成。如果显示canceled,那可能是浏览器主动取消了(比如页面跳转了、用户关了标签)。
第二步:检查请求的URL有没有拼错
// 这种小错误太常见了
fetch('/api/v1/user' + userId) // 忘了加 / 分隔符
fetch(`http://localhost:3000/api/users/${userId}`) // 端口号写错
你在Console里打一条:
console.log('请求地址:', `http://localhost:3000/api/users/${userId}`);
把拼出来的完整地址复制出来,用浏览器直接访问,看能不能通。如果直接访问都404,那问题就在地址上。
第三步:看Request Method对不对
有些后端接口只接受POST,你用了GET,或者反过来。在Network面板里看Method列,确认是GET、POST、PUT还是DELETE。
fetch('http://api.example.com/users', {
method: 'POST', // 别漏了这个,默认是GET
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: '张三', age: 25 })
})
跨域问题——AJAX的”拦路虎”
跨域是新手遇到最多的问题之一。我先讲清楚什么是跨域,不然你后面的操作都是盲目的。
什么是跨域
浏览器有个安全机制叫”同源策略”,意思是:你的页面只能和同一个源的服务器通信。所谓”同源”,就是协议、域名、端口三个都要一致。
http://example.com:80/api ← 和这个同源
https://example.com:80/api ← 协议不同,跨域
http://api.example.com:80/api ← 域名不同,跨域
http://example.com:3000/api ← 端口不同,跨域
如果跨域了,浏览器会直接拦截响应,你在控制台看到类似这样的报错:
Access to fetch at 'http://api.example.com/users' from origin
'http://localhost:8080' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
怎么解决跨域
跨域问题不是前端单方面能解决的,需要前后端配合。常见方案有几种:
方案一:后端设置CORS响应头(最推荐)
后端在返回响应时加上这几个头:
// Node.js + Express 示例
const express = require('express');
const app = express();
app.use((req, res, next) => {
// 允许指定域名访问
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
// 允许的请求方法
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// 允许的请求头
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 如果前端带了凭证(cookie),这里必须设为true
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
app.get('/api/users', (req, res) => {
res.json({ users: ['张三', '李四'] });
});
注意:Allow-Credentials设为true时,Allow-Origin不能写*,必须写具体域名。
方案二:用代理服务器(前端开发时最方便)
如果你只是想本地开发时绕过跨域,可以在前端项目里配一个代理:
// vite.config.js 配置
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000', // 后端地址
changeOrigin: true, // 篡改请求头中的Host
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};
配置之后,前端请求/api/users就会自动转发到http://localhost:3000/users,浏览器看到的是同源请求,完全绕过跨域限制。
方案三:JSONP(老项目可能还在用)
JSONP是很久以前的方案,利用<script>标签不受同源策略限制的特点。现在新项目基本不需要了,但有些老旧API还在用:
// 前端用JSONP方式请求
function jsonp(url, callback) {
const script = document.createElement('script');
const callbackName = 'jsonp_' + Date.now();
window[callbackName] = function(data) {
callback(data);
document.body.removeChild(script);
};
script.src = `${url}?callback=${callbackName}`;
document.body.appendChild(script);
}
// 后端需要把数据包装成函数调用的形式
// 返回:jsonp_1234567890({ users: ['张三', '李四'] })
超时问题——请求发出去就没了
超时是指请求在限定时间内没有收到响应,浏览器会主动放弃。这个错误信息通常长这样:
Failed to fetch
或者
NetworkError when attempting to fetch resource.
或者在某些浏览器里直接显示:
Request timeout
设置超时
用fetch的话,可以配合AbortController来实现超时控制:
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
return fetch(url, {
...options,
signal: controller.signal
}).finally(() => clearTimeout(timeoutId));
}
// 使用
fetchWithTimeout('http://api.example.com/users', {
method: 'GET'
}, 3000) // 3秒超时
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求超时了,3秒内没收到响应');
} else {
console.log('其他错误:', err);
}
});
用XMLHttpRequest的话更直接:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://api.example.com/users');
xhr.timeout = 3000; // 设置超时时间,单位毫秒
xhr.ontimeout = function() {
console.log('请求超时了');
};
xhr.onerror = function() {
console.log('网络错误');
};
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
超时了怎么办
超时不等于服务器挂了,可能是:
- 服务器处理太慢,你在等它查数据库、调第三方接口
- 网络本身慢,请求在路上花了很多时间
- 服务器压根没收到请求(比如跨域被浏览器拦了,但错误信息不明显)
排查思路:先直接在后端日志里查有没有收到这个请求。如果后端日志里完全没有,说明请求根本没到达服务器,问题在前端网络层。如果后端日志里有记录但处理很慢,那就是后端性能问题了。
参数传递——数据发对了没
参数传递是AJAX里最容易出小毛病的地方,而且这些毛病往往悄无声息,不像跨域那样直接报错。
GET请求传参
// 方式一:URL拼参数
const userId = 1001;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => console.log(data));
// 方式二:query string
const params = new URLSearchParams({
page: 1,
size: 20,
keyword: '张三'
});
fetch(`/api/search?${params}`)
.then(res => res.json())
.then(data => console.log(data));
注意: GET请求的参数要放在URL里,不要放在body里。有些后端框架对GET请求的body会直接忽略,导致数据丢了。
POST请求传参
// 发送JSON数据(最常见)
fetch('http://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // 这行很重要!
},
body: JSON.stringify({
name: '张三',
age: 25,
email: 'zhangsan@example.com'
})
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('请求失败:', err));
// 发送表单数据
const formData = new FormData();
formData.append('name', '张三');
formData.append('age', '25');
formData.append('avatar', fileInput.files[0]); // 可以传文件
fetch('http://api.example.com/users', {
method: 'POST',
body: formData
// 注意:用FormData时不要手动设置Content-Type,
// 浏览器会自动加上 boundary,手动设置会导致后端解析失败
})
一个新手常犯的错误:
// 错误!漏了headers,后端收到的是raw body而不是JSON对象
fetch('http://api.example.com/users', {
method: 'POST',
body: JSON.stringify({ name: '张三' })
});
// 正确!加上Content-Type
fetch('http://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: '张三' })
});
没有Content-Type头,后端可能不知道该怎么解析请求体,轻则数据为空,重则直接报500错误。
参数编码问题
如果参数里有中文或者特殊字符,一定要编码:
const keyword = 'JavaScript教程';
// 错误:直接拼,中文可能乱码
fetch(`/api/search?q=${keyword}`);
// 正确:用encodeURIComponent
fetch(`/api/search?q=${encodeURIComponent(keyword)}`);
// 或者用URLSearchParams,它会自动编码
const params = new URLSearchParams({ q: keyword });
fetch(`/api/search?${params}`);
从Network面板里能学到的事
前面聊了这么多,最后把网络面板的用法系统化一下。这是你调试AJAX最强大的工具。
看什么
打开Network面板后,每次请求点进去,有几个关键部分要看:
1. Headers标签
- Request Headers:看看你的请求头发对了没有。比如
Content-Type是不是application/json,有没有带Authorizationtoken。 - Response Headers:看看服务端返回了什么。如果跨域了,这里应该有
Access-Control-Allow-Origin。
2. Payload(或Request)标签
- 这里显示你实际发出去的数据。如果你发了JSON但这里显示的是字符串,说明你可能忘了
JSON.stringify。 - 如果是表单提交,这里能看清每个字段是否正确。
3. Response标签
- 服务端的返回内容。如果请求成功但数据不对,先看这里。
- 如果是HTML而不是JSON,说明请求可能打到错误的路由了,或者后端配置有问题。
4. Timing标签
- 这个最有用!它把请求的整个过程拆解成了几个阶段:
- Queueing:请求在队列里等待的时间,如果这个时间很长,说明前面有很多请求堵着
- Stalled:请求卡住的时间,可能是DNS解析或连接建立的问题
- DNS Lookup:域名解析时间
- Initial connection:建立连接的时间
- SSL:HTTPS握手时间
- Request sent:发送请求的时间
- Waiting for response(TTFB):从发出请求到收到第一个字节的时间——这个值最能反映后端性能
- Content download:下载响应体的时间
如果你看到TTFB特别长(比如好几秒),那就是后端处理慢,不是前端的问题。
过滤请求
当页面请求很多的时候,用过滤很方便:
- 点Network面板上方的
Fetch/XHR按钮,只显示AJAX请求 - 在Filter框里输入关键词,比如
/api,过滤出所有API请求 - 右键请求可以Copy as cURL,直接拿到完整的请求命令,方便拿到Postman里复现
一个完整的排查流程
把上面所有内容串起来,给你一个实际遇到pending状态时的排查清单:
1. 请求出现在Network面板里了吗?
└── 没有 → 检查事件绑定、函数调用时机
└── 有 → 继续
2. 状态是什么?
└── canceled → 页面跳转了或用户关了标签
└── (failed) → 网络不通,检查URL、服务器是否启动
└── pending → 继续往下查
3. 看Timing标签
└── Stalled/DNS/Connection时间长 → 网络或DNS问题
└── TTFB时间长 → 后端处理慢
└── 其他都正常但一直pending → 可能跨域被拦
4. 看Response Headers
└── 没有Access-Control-Allow-Origin → 跨域问题,找后端加头
└── 有但还是有报错 → 检查credentials配置是否匹配
5. 看Request Headers和Payload
└── Content-Type不对 → 补上正确的header
└── 参数为空 → 检查数据有没有正确序列化
└── 中文乱码 → 用encodeURIComponent编码
6. 看Console
└── 有红色报错 → 根据报错信息解决
└── 没有报错但请求失败 → 可能是静默失败,加try-catch或.catch捕获
最后说几句
AJAX请求失败这事儿,本质上就一句话:把问题定位到具体环节。是发不出去、传不过去、还是收不回来?每个环节都有对应的排查工具和解决方法。
我见过太多新手一遇到pending就慌,然后到处问”为什么我的请求没反应”。其实你只需要冷静地打开Network面板,按步骤看一遍,大部分问题都能在五分钟内定位。剩下的就是对症处理——跨域就配CORS,超时就设AbortController,参数错了就检查序列化。
记住,浏览器开发者工具是你最强的盟友,它不会骗你,它会告诉你一切。善用它,AJAX再也不怕。
