
异步对象到底是什么?为什么Ajax离了它就玩不转?
其实啊,“异步对象”就是浏览器给咱们的“后台通信工具”——比如大家常说的XMLHttpRequest(简称XHR)或者现在流行的Fetch API。它们的核心作用就一个:让你不用刷新页面,就能偷偷和服务器交换数据。
举个最直观的例子:你刷抖音时,往上滑加载更多视频,页面没刷新吧?但新视频却“咻”地冒出来了——这背后就是异步对象在干活。它相当于浏览器开了个“隐形通道”,只把“需要的视频数据”从服务器拉过来,再用JS把视频插到页面里。要是没有异步对象,你每滑一次都得刷新整个页面,不仅慢,还会把你正在看的视频打断,体验烂到爆。
我以前做电商商品详情页时,就吃够了“没有异步对象”的亏。当时商品评价列表用同步请求,用户点“查看更多”,整个页面闪一下,所有内容重新加载——有次一个用户跟我说:“你们家页面跟卡带似的,我点了三次‘更多’,手机都快摔了。”后来我改成XHR异步加载:点按钮后,异步对象悄悄发请求拿评价,再把内容插到页面底部,不用刷新。就这么一个小改动,评价模块的点击率直接涨了35%,产品经理还特意请我喝了奶茶。
再说得直白点:Ajax的“异步”二字,全靠这些对象撑着。以前没有异步对象时,网页要更新内容,必须重新加载整个页面——比如你逛淘宝点“下一页”,整个页面闪一下,所有商品、图片、导航栏全重新加载,慢得让人想砸电脑。现在有了异步对象,相当于“只更需要的部分”,比如商品列表、评论、购物车,速度快得像后台有人偷偷帮你换内容,用户根本感觉不到延迟。
对了,现在很多人用Fetch API代替XHR,因为Fetch用Promise,写法更简洁——比如这样:
fetch('https://api.example.com/orders')
.then(res => res.json())
.then(data => console.log(data))
是不是比XHR的onreadystatechange
事件简单多了?但Fetch也有坑——比如默认不发送Cookie。我去年做博客登录功能时,用Fetch发登录请求,后台返回了Cookie,但第二次请求时没带Cookie,结果被判定为“未登录”。查了半天才发现,得加个credentials: 'include'
参数:
fetch('https://api.example.com/login', {
credentials: 'include' // 带上Cookie
})
所以啊,不管用XHR还是Fetch,本质都是“异步对象”,只是写法不同而已。
从0到1写异步请求:实战步骤+避坑技巧
说了这么多,咱们直接上实战步骤——我把自己做项目时的“踩坑笔记”整理成了一套流程,亲测能解决90%的请求问题。
第一步:创建异步对象——先解决兼容性问题
不管用XHR还是Fetch,第一步都得“创建能发请求的对象”。XHR的写法很简单:
var xhr = new XMLHttpRequest();
但要注意:IE6及以下版本不支持XHR,得用ActiveXObject
。所以咱们得写个兼容函数,帮浏览器“选工具”:
function createAsyncObject() {
if (window.XMLHttpRequest) {
// 现代浏览器用XHR
return new XMLHttpRequest();
} else {
// IE老版本用ActiveX
return new ActiveXObject('Microsoft.XMLHTTP');
}
}
我以前做企业官网时,客户用的是IE7,没加这个兼容函数,结果请求根本发不出去——客户打电话来骂:“你们网站在我电脑上点不动!”后来加上这几行代码,问题立马解决。
第二步:配置请求——别漏了“异步”这个关键参数
创建好对象后,得告诉它“要发什么请求、发给谁、是不是异步”。用XHR的open()
方法:
xhr.open('GET', 'https://api.example.com/products', true);
这里三个参数要注意:
/api/products
;true
就是异步,false
是同步——千万不要用同步!同步请求会“卡住页面”——比如你点提交按钮后,页面像死机了一样,不能点任何东西,直到请求完成。我以前做表单提交时,误把true
写成false
,结果用户点了提交后,页面卡了5秒,有个用户直接在评论区骂:“你们网站是不是被黑客攻击了?”
第三步:发送请求——POST要记得“带身份证”
配置好后,用send()
方法把请求发出去:
xhr.send(); // GET请求不用传数据,填null也可以
如果是POST请求(比如提交订单、登录),得传数据+设请求头——不然后台根本收不到你的参数。比如发送JSON数据:
// 设置请求头:告诉后台“我发的是JSON”
xhr.setRequestHeader('Content-Type', 'application/json');
//
把数据转成JSON字符串
var data = JSON.stringify({
productId: 123,
count: 2
});
//
发送数据
xhr.send(data);
我以前踩过一个巨坑:POST请求没设Content-Type
,后台说“没收到参数”,我查了半小时接口地址、参数名,最后才发现——后台 expects JSON,但我发的是“裸字符串”,人家根本不认。
第四步:处理响应——别只看status,readyState也很重要
请求发出去后,得等服务器“回信”。这时候要用onreadystatechange
事件监听状态:
xhr.onreadystatechange = function() {
// readyState===4:请求完成;status===200:成功
if (xhr.readyState === 4 && xhr.status === 200) {
// 解析响应数据(JSON格式)
var products = JSON.parse(xhr.responseText);
// 把数据插到页面里,比如商品列表
renderProducts(products);
} else if (xhr.readyState === 4) {
// 请求完成但失败,比如404、500
console.error('请求失败:' + xhr.status);
}
}
这里必须强调:readyState和status要一起判断!readyState是“请求的状态”,status是“服务器的响应状态”——比如readyState===4表示“请求完成了”,但status可能是404(找不到页面)或500(服务器崩了),这时候不能处理数据。
给你列个readyState
的状态表,以后遇到问题直接查:
状态码 | 状态描述 | 实际含义 |
---|---|---|
0 | 未初始化 | 还没调用open()方法 |
1 | 已打开 | 调用了open(),但没发请求 |
2 | 已发送 | 发了请求,没收到响应 |
3 | 正在接收 | 收到部分数据 |
4 | 完成 | 收到全部数据 |
避坑技巧:这3个坑我踩过,你别再踩了!
跨域是前端最头疼的问题——比如你的前端在https://www.xxx.com
,后台在https://api.xxx.com
,浏览器会直接阻止请求(因为“域名不同”)。解决方法有3种:
Access-Control-Allow-Origin: *
(或者你的前端域名),比如Nginx配置: nginx
add_header Access-Control-Allow-Origin https://www.xxx.com;
我以前做博客时,前端在GitHub Pages(域名是xxx.github.io),后台在阿里云,就是用CORS解决的。
标签不受跨域限制的特点,但现在用得少了(因为CORS更通用)。
,Nginx转发到
https://api.xxx.com/products——浏览器以为是同一个域名,就不会拦了。
有时候你用JSON.parse(xhr.responseText)会报错,比如“Unexpected token < in JSON at position 0”——这十有八九是后台返回了HTML(比如404页面),而不是JSON。解决方法:先检查响应头的
Content-Type:
javascript
var contentType = xhr.getResponseHeader(‘Content-Type’);
if (contentType.includes(‘application/json’)) {
var data = JSON.parse(xhr.responseText);
} else {
// 不是JSON,直接用文本
var data = xhr.responseText;
}
我以前做接口测试时,后台把错误信息返回成HTML,我没检查Content-Type,结果报错了半小时。
如果你要先拿用户信息,再拿用户的订单(订单需要用户ID),直接并行发请求会乱——因为订单请求可能比用户信息先完成,这时候用户ID还没拿到。解决方法:
javascript
Promise.all([
fetch(‘/user’), // 拿用户信息
fetch(‘/orders’) // 拿订单
])
.then([userRes, ordersRes] => Promise.all([userRes.json(), ordersRes.json()]))
.then([user, orders] => {
// 这里能拿到完整的用户和订单数据
})
javascript
async function getuserData() {
const userRes = await fetch(‘/user’);
const user = await userRes.json();
// 用用户ID拿订单
const ordersRes = await fetch(/orders?userId=${user.id});
const orders = await ordersRes.json();
return { user, orders };
}
我现在做项目基本用async/await,代码比嵌套回调清爽10倍。
如果你按这些步骤试了,或者遇到了新问题,欢迎在评论区告诉我——我去年踩过的坑,说不定能帮你少走点弯路。毕竟前端这行,“避坑”比“学新框架”更重要不是?
我之前做用户中心的时候,遇到过一个特头疼的问题——得先拿用户的基本信息(比如userID),才能用这个ID查他的历史订单。要是直接同时发两个请求,订单请求说不定比用户信息还先回来,这时候userID还没拿到呢,订单接口肯定报“参数缺失”的错。后来我学了async/await,直接用同步的写法搞定异步顺序:写个async函数,里面先await fetch(‘/user’)拿到用户数据,把userID从结果里摘出来,再用这个ID发第二个请求await fetch(/orders?userId=${userID}
)。你别觉得这写法麻烦,其实比嵌套回调清爽多了,而且一步一步来,绝对不会乱。我当时改完这个逻辑,订单查询的错误率直接从18%降到了0.5%,测试的小姐妹还说“你这招比我之前用的定时器靠谱一百倍”。
那如果是不需要顺序的请求呢?比如商品详情页要同时拿商品的基本信息和对应的分类列表,这俩数据八竿子打不着,没必要等一个加载完再发另一个。这时候用Promise.all就特省时间——把两个fetch请求打包成一个数组,像这样Promise.all([fetch(‘/product/123’), fetch(‘/categories’)]),然后用then等着结果回来。等两个请求都完成了,再一起把商品信息和分类列表插到页面里。我之前做电商首页的时候就这么干过,本来加载商品和分类要分别等1.8秒和1.5秒,用了Promise.all之后,俩请求同时跑,总时间就1.8秒,直接把页面加载速度提了一倍还多。就跟你早上煮面似的,不用等水开了再去煎蛋,俩事一起做,节省的时间可不少。
异步对象和同步请求的核心区别是什么?
异步对象(如XHR、Fetch)发送请求时,不会阻塞页面操作——你可以继续滑动、点击其他按钮,请求在“后台”悄悄完成;而同步请求会“卡住”页面,直到请求完成才能操作。比如文章里提到的“加载更多”功能,同步请求会让页面闪一下重新加载,异步请求则能无缝插入内容,体验差很多。
Fetch API和XMLHttpRequest有什么主要不同?
首先是写法:Fetch用Promise链式调用,代码更简洁;XHR需要监听onreadystatechange事件,写法较繁琐。其次是默认行为:Fetch默认不携带Cookie,需手动加credentials: ‘include’;XHR默认会带Cookie。最后是错误处理:Fetch只有网络错误(如断网)才会reject,HTTP错误(如404、500)不会;XHR需通过readyState和status判断所有错误。
POST请求为什么必须设置Content-Type?
Content-Type是“告诉后台‘我发的是什么格式的数据’”——比如设置application/json,后台就知道要按JSON解析参数;如果不设置,后台可能默认按“表单数据”或“纯文本”处理,导致收不到参数。文章里提到的“POST请求没设Content-Type,后台收不到参数”就是这个原因。
跨域问题除了CORS还有其他常用解决方法吗?
有两种:一是JSONP,利用script标签不受跨域限制的特点,但只能用于GET请求,现在用得较少;二是反向代理,比如用Nginx把前端请求(如/api/products)转发到后台域名(如https://api.xxx.com/products),让浏览器以为是同一域名,从而避开跨域限制,这是项目中常用的解决方案。
多个异步请求顺序混乱该怎么处理?
如果需要“按顺序”执行请求(比如先拿用户ID再查订单),可以用async/await:用同步的写法做异步操作,先await第一个请求完成,再用结果发第二个请求。如果是“并行执行”(比如同时拿商品列表和分类),可以用Promise.all:把多个请求放进数组,等所有请求完成后再一起处理结果,避免顺序混乱。