封装 AJAX

上一篇文章介绍了 AJAX 的实现原理,这一篇文章自己动手封装一个 AJAX 请求,最后再用 Promise 修改一下。

在开始之前,首先思考两个问题:

  • JS 可以设置任意请求 header 吗 ?
1
2
3
4
第一部分 request.open(method,'/xxx')
第二部分 request.setHeaders('Content-Type':'x-www-form-urlencoded')
第三部分 为回车,不需要设置
第四部分 request.send('a=1&b=2'),只有 method 为 post 时才可传递数据,默认 get 方式不传递数据
  • JS 可以获取任意响应 header 吗 ?
1
2
3
4
第一部分 request.status / request.ststusText
第二部分 request.getReponseHeader() / getAllResponseHeaders()
第三部分 为回车,不需要设置
第四部分 request.reponseText

一、实现 jQuery

1、代码实现

这篇文章以 jQuery 入门为基础,主要讲解的是 jQuery 实现原理以及封装了几个方法。大概写下实现原理为:

1
2
3
4
5
6
window.jQuery = function(nodeOrSelector){
let nodes = {};
nodes.addClass = function(){}
nodes.html = function(){};
return nodes;
}

本文使用的代码延续上一篇文章。针对此文章的代码,对 index.html 文件中的 JS 做进一步的封装,修改页面内 script 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
window.jQuery = function(nodeOrSelector){
let nodes = {};
nodes.addClass = function(){}
nodes.html = function(){};
return nodes;
}
window.jQuery.ajax = function(url,method,body,successFn,failFn){
let request = new XMLHttpRequest();
request.open(method,url);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status < 300){
successFn.call(undefined,request.responseText)
}else if(request.status >= 400){
failFn.call(undefined,request)
}
}
}
request.send(body);
}
window.$ = window.ajax;
btn.addEventListener('click',function(){
window.jQuery.ajax(
'/xxx',
'post',
'a=1&b=2',
(res) => {console.log(1)},
(res) => {console.log('fail')}
)
})

以上代码能够实现基本的 ajax 请求,但同时也存在两个问题:

  • get 请求不存在请求体,即第三个参数为空
  • 参数含义不明确,对第三方使用者极其不友好

2、代码优化

以上,修改点击事件和封装函数,将参数以 object 形式传递到 window.jQuery.ajax 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
window.jQuery.ajax = function(options){
let url = options.url;
let method = options.method;
let body = options.body;
let successFn = options.successFn;
let failFn = options.failFn;

let request = new XMLHttpRequest();
request.open(method,url);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status < 300){
successFn.call(undefined,request.responseText)
}else if(request.status >= 400){
failFn.call(undefined,request)
}
}
}
request.send(body);
}
btn.addEventListener('click',function(){
window.jQuery.ajax({
method : 'post',
url : '/xxx',
successFn : () => {console.log('success')},
failFn : () =>{console.log('fail')}
})
})

3、继续优化:添加请求headers

添加请求 headers ,只需要在请求参数中将请求头以对象的形式传递过去就可以,然后在 window.jQuery.ajax 中通过循环的方式去设置请求头,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
window.jQuery.ajax = function(options){
let request = new XMLHttpRequest();
let url = options.url;
let method = options.method;
let body = options.body;
let successFn = options.successFn;
let failFn = options.failFn;
let headers = options.headers;
for(var key in headers){
let val = headers[key];
request.setRequestHeader(key,val);
}
...
btn.addEventListener('click',function(){
window.jQuery.ajax({
method : 'post',
url : '/xxx',
headers : {
'content-type' : 'application/x-www-form-urlencoded',
'summer' : 10
},
successFn : () => {console.log('success')},
failFn : () =>{console.log('fail')}
})
})
...

4、继续优化,传参数不定

通过查询 jQuery 官方文档,可以看出,在调用 ajax 时的传参可以是一个,也可以是两个,如:

1
2
$.ajax(url[,settings])
$.ajax([settings])

为保持自己封的接口同 $.ajax 一致,将上述代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
window.jQuery.ajax = function(options){
let url;
if(arguments.length ==== 1){
url = options.url;
}else if(arguments.length ==== 2){
url = arguments[0];
options = arguments[1]
}
let request = new XMLHttpRequest();
let method = options.method;
let body = options.body;
...

二、ES6 语法 -> 结构赋值

解构又称析构,解释起来比较复杂,就是通过结构表达式使表达式右边的值与左边的变量一一对应,还是举例说明:

1
2
let [a,b,c] = [1,2,3];      //通过数组对多个变量赋值
let { name ,age } = { name : 'Summer' , age : 12} //通过一个对象给多个变量赋值

根据此特性,可以将上述封装的 ajax 方法再次进行优化:

1
2
3
4
5
6
7
8
9
10
11
12
...
let request = new XMLHttpRequest();
let url;
if(arguments.length === 1){
url = options.url;
}else if(arguments.length === 2){
url = arguments[0];
options = arguments[1];
}
let {method,body,successFn,failFn,headers} = options;
request.open(method,url);
...

此处,由于多个传参比较难处理,现该用只有一个传参:

1
2
3
4
5
6
...
window.jQuery.ajax = function(options){
let request = new XMLHttpRequest();
let {url,method,body,successFn,failFn,headers} = options;
request.open(method,url);
...

根据以上代码,我们只需将 options 进行赋值即可,为了简便,我们可以直接将结构表达式左侧的部分放入传参中:

1
2
3
4
5
...
window.jQuery.ajax = function({url,method,body,successFn,failFn,headers}){
let request = new XMLHttpRequest();
request.open(method,url);
...

三、Promise 与 jQuery

上述对 ajax 的封装,对于请求成功/失败后的函数回调,即 successFn 和 failFn ,如果我们不看文档就不知道函数回调的命名规则。为了解决这个问题,我们采用 ES6 中的 Promise 方法实现。
Promise 是一种确定函数形式的规范。

1、jQuery 中的 Promise

为了完成 ajax 请求的 Promise 改造,我们需要先了解 Promise 。首先引入 jQuery,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
function f1(responseText){}
function f2(responseText){}
btn.addEventListener('click',function(){
$.ajax({
method : 'post',
url : '/xxx',
success : (x) => {
f1.call(undefined,x);
f2.call(undefined,x);
},
error : () =>{console.log('fail')}
})
})
...

实际上,在 jQuery 中已经实现了对 Promise 的实现,上述代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
function success(responseText){
console.log(responseText);
}
function error(request){
console.log(request);
}
btn.addEventListener('click',function(){
$.ajax({
method : 'post',
url : '/xxx'
}).then(success,error);
})
...

以上,如果成功了,就会直接调用 then 里面的 success 方法,反之调用 error 方法。
根据上述方法,成功后仍旧需要调用一个已命名的方法,看似份 jQuery 中的 success 、 error 相同,实际上,以上成功回调可以没有函数名,也就是以匿名函数的形式进行调用:

1
2
3
4
5
6
7
8
$.ajax({
method : 'post',
url : '/xxx'
}).then((responseText) => {
console.log(responseText);
},(request) => {
console.log('error');
});

以上方法的意义在于不需要再去传递一个封装函数中已定义的函数名的函数

2、jQuery 中 then() 方法多次调用

这种情况出现在 在请求数据完成后,执行完成 fn1() 后再执行 fn2() 、 fn3() 依此类推。具体代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
$.ajax({})
.then(
(responseText) => {
console.log(responseText);
return responseText;
},
(request) => {
console.log('error');
return '请求失败'
}
)
.then(
(responseText) => {
console.log(responseText);
},
(request) => {
console.log(request);
}
)
...

如果请求成功,则会执行第一个 then 中的 resolve 方法,并将返回值传递到第二个 then 方法中,请求失败同理。这种方法可以实现对请求结果的多次处理。以上是 jQuery 中的 Promise 实现方式,那么如果自己做该如何实现呢?

3、Promise 简单实现 jQuery 封装

直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
...
// ajax 声明
window.jQuery.ajax = function({url,method,body,headers}){
return new Promise(function(resolve,reject){
let request = new XMLHttpRequest();
request.open(method,url);
for(var key in headers){
let val = headers[key]
request.setRequestHeader(key,val)
}
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status < 300){
resolve.call(undefined,request.responseText)
}else if(request.status >= 400){
reject.call(undefined,request)
}
}
}
request.send(body);
})

}
// ajax 调用
btn.addEventListener('click',function(){
window.jQuery.ajax({
method : 'post',
url : '/xxx1',
headers : {
'content-type':'application/x-www-form-urldecoded',
'summer':'18'
}
})
.then(
(responseText) => {console.log(responseText)},
(request) =>{console.log(request)}
)
})
...

其实到这里个人对 Promise 的理解还不是很透彻。后续会专门写一篇文章讲解下 Promise 。