最近写主题的时候遇到了这个需求,为了优化用户体验,需要使用AJAX来提交评论。以前都是在网上CV别人的代码修修补补 ,这次想着借此机会巩固一下自己学习到的基础知识 ,于是就小小地尝试了一番
基本流程
原生JS的AJAX请求主要是通过XMLHttpRequest
这个对象实现的。首先来看一下AJAX请求的基本流程:
- 创建
XMLHttpRequest
对象 - 设置请求方式、地址和数据
- 发送请求
- 监听状态变化
- 接受数据
根据以上五步,翻阅MDN文档时我们可以很轻松地找到我们需要的属性和方法。
1.创建XMLHttpRequest
对象
XMLHttpRequest
有一个构造函数XMLHttpRequest()
,我们可以直接new
一个XMLHttpRequest
示例对象出来
let xhr = new XMLHttpRequest();
2.设置请求参数
XMLHttpRequest.open()
方法用于初始化一个请求,我们就是使用这个方法来设置我们的参数,该方法接受的参数如下:
method
-> 请求使用的HTTP
方法(GET, POST, ...)url
-> 请求的目标urlasync
-> 是否继续异步操作(bool)user
-> 用于认证的用户名password
-> 用户认证的密码
其中,后面三个参数为可选参数,毕竟不是每一个http请求都需要认证,user
和password
的缺省值都是null
。而async
的缺省值为true
。也就是说XHR请求默认就是异步的。为什么不同步呢?因为由于Javascript是单线程的,如果在主线程上发送同步XHR请求结果卡住了,会导致整个线程堵塞。MDN上给出的解释也是如此
注意:主线程上的同步请求很容易破坏用户体验,应该避免;实际上,许多浏览器已完全弃用主线程上的同步XHR支持。
到这里就比较清晰了,我们想要对目标url
发送一个请求时,只需要这样简单配置一下。
xhr.open('POST', url);
xhr.open('GET', url);
3.设置状态监听的方法
因为要在发送请求后监听状态变化,并根据不同的状态以此进行不同的处理。再发送之前我们需要先设置好状态变更时的回调函数。而XMLHttpRequest
对象有着这样一个属性:onreadystatechange
,MDN上的描述是这样子的
当readyState
属性发生变化时,调用的event handler
。
也就是说我们可以这样子传入一个回调函数
xhr.onreadystatechange = function() {
console.log('yo! 状态变化了');
};
那么我们怎么知道我们已经完成了一次请求,并且可以接受数据了呢?答案就在readyState
属性里头,通过读取XMLHttpRequest.readyState
我们可以判断当前的请求状态
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open() 方法。 |
1 | OPENED | open() 方法已经被调用。 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且头部和状态已经可获得。 |
3 | LOADING | 下载中; responseText 属性已经包含部分数据。 |
4 | DONE | 下载操作已完成。 |
那么,我们只需要在回调函数中加入一个简单的判断,就可以知道现在是啥情况了。
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
console.log('请求已经完成辣!');
}
};
既然请求已经完成,我们至少需要知道他的HTTP返回码和返回内容,而这些东西我们可以通过XMLHttpRequest.status
和XMLHttpRequest.responseText
获取。
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
console.log('返回的状态码为:', xhr.status);
console.log('返回的文本为', xhr.responseText);
}
};
我们也可以根据返回的状态码去写不同的回调函数。例如,当返回2xx时,调用成功的回调函数;返回4xx,5xx时,调用失败的回调函数。举个最简单的例子
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
callSuccess(xhr.responseText);
else
callError(xhr.responseText);
}
}
4.发送!
一切都准备就绪了,就差发送按钮发射了 。XHR请求的发送也很简单,只需要调用send()
方法即可。
xhr.send();
接下来就交给XHR自己去跑吧~
5.封装
总结上述的知识,我们可以对xhr请求的GET
和POST
进行简单的封装。
let Ajax = {
get: function (url, resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
resolve(xhr.responseText);
else
reject(xhr.responseText);
}
}
xhr.send();
},
post: function (url, data, resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
resolve(xhr.responseText);
else
reject(xhr.responseText);
}
}
xhr.send(data);
}
}
通过这样的封装,我们可以更加方便地发送一个HTTP请求,就像这样:
Ajax.get('https://i.exia.xyz', (success) => {
console.log('成功了!返回结果为:', JSON.parse(success));
}, (error) => {
console.log('哎呀,出错了,', JSON.parse(error));
});
不过,这个样子好像还是差了点啥。说起异步,好像总得扯到Promise
。没错,还得给他用Promise
封装一下。稍加改造,我们就可以得到下面的代码
let Ajax = {
get: function (url, resolve, reject) {
new Promise((rs, rj) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
rs(xhr.responseText);
else
rj(xhr.responseText);
}
}
xhr.send();
}).then(success => {
resolve(success);
}).catch(error => {
reject(error);
});
},
post: function (url, data, resolve, reject) {
new Promise((rs, rj) => {
let xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
rs(xhr.responseText);
else
rj(xhr.responseText);
}
}
xhr.send(data);
}).then(success => {
resolve(success);
}).catch(error => {
reject(error);
});
}
}
真不戳,加上了Promise
感觉可靠性都高了不少。不过仔细一看,又有点说不上来的奇怪,我Promise
不应该是返回一个Promise
对象方便之后的链式调用吗。嘿!这才对!再次稍加改造,就可以得到下面这个Ajax
对象
let Ajax = {
get: function (url) {
return new Promise((rs, rj) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
rs(xhr.responseText);
else
rj(xhr.responseText);
}
}
xhr.send();
});
},
post: function (url, data) {
return new Promise((rs, rj) => {
let xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)
rs(xhr.responseText);
else
rj(xhr.responseText);
}
}
xhr.send(data);
});
}
}
这个时候,调用的方式就需要有所变化了,经典的Promise
链式玩法就出现辽。
Ajax.get('https://i.exia.xyz').then(success => {
console.log('成功了!返回结果为:', JSON.parse(success));
}).catch(error => {
console.log('哎呀,出错了,', JSON.parse(error));
})
这个时候的功能基本上已经完成了,要说的话,可以再完善一下请求成功的判断条件
if(xhr.status === 200)
//改为
if(xhr.status >= 200 && xhr.status < 300)
也可以再在调用resolve()
或者reject()
的时候更好地传递一下参数
rs({
code: xhr.status,
response: xhr.responseText
});
这样子,可能就能避免一些后端的奇妙操作带来的后果
以上,就是这次我尝试封装一个简单Ajax对象的过程,代码如果有什么问题欢迎大家指出~
直接fetch不好吗,干嘛自己封xhr呢
简单来说就是刚刚学到一些东西想着怎么也给用一下的感觉