乐趣区

[ 造轮子 ] 手动封装 AJAX (三) —— 最终版

导言
在开始之前先想一想 ajax 是怎样的流程

首先打开一个连接
发送数据
返回结果

我们要自定义的设置有哪些

设置请求方式
设置请求头
设置返回数据格式
返回成功后或失败后

我们要做的功能有哪些

数据校验
统一数据的格式
支持文件上传
对于传入参数的容错处理

经过以上思考基本结构大致成型

数据校验
数据格式的统一
建立连接
设置请求头
设置返回数据格式
发送数据
返回成功或失败

代码如下
class AJAX {
constructor({url = “”,method = “GET”,data = {},async = true,success,error,resType = “”,headers = {}}) {
// 集中管理传递过来的参数
this.option = {url,method,data,async,success,error,resType,headers};
this.xhr = new XMLHttpRequest();
this.start();
}
start() {
// 数据校验
this.checkOption();
// 数据格式的统一
this.initOption();
// 建立连接
this.open();
// 设置请求头
this.setHeaders();
// 设置返回数据格式
this.setResponseType();
// 发送数据
this.sendData()
// 返回成功或失败
this.responseData();
};

}
接下来添加校验功能

首先 url 不能是空
然后请求头必须是字面量对象格式 {key:value}
再有就是一些简单的警告

代码如下
checkOption() {
let {url,async,resType,headers} = this.option;
if (url === ”) {
throw new Error(‘ 请求地址不能为空 ’); // 打印错误信息,并停止当前进程
//Console.error(‘ 请求地址为空 ’); 也可以打印错误信息,但是不能停止当前进程
}

if(typeof headers !== ‘object’){
throw new Error(‘ 设置请求头时请传入 {key:value,key:value…} 的格式 ’);
}

if(typeof resType !== ‘string’){
throw new Error(‘ 设置返回数据格式时请传入字符出串格式 ’);
}

if (typeof url !== ‘string’) {
// 输出警告信息
console.warn(‘ 当前请求地址不是字符串,现在将其尝试转换为字符串 ’);
}
if (async === false && resType != ”) {
console.warn(‘ 如果设置了请求方式为同步,即使设置了返回数据格式也不会生效 ’);
}
};
需要注意的是返回数据格式可以设置这几个值,之后会写一个详细的传参指南

接下来是数据的处理

首先我们需要保证请求格式,不管传入时是大写还是小写,在我们设置请求格式时要是全部大写
还有就是 url 可能是数字的,需要转换成字符
为了方便将 async 不是布尔型的转成布尔型,这是什么概念,就是传参时 写数字 1 是异步 数字 0 是同步
将需要发送的内容做一个处理

initOption() {
let {url,async,method} = this.option;
//url 不是字符串转换成字符串
if (typeof url !== ‘string’) {
try {
this.option.url = url.toString();
console.log(` 转换成功: “${this.option.url}”`);
} catch (error) {
throw new Error(‘url 转换字符串失败 ’);
}
}
//async 不是布尔型转成布尔型
if(typeof async !==’boolean’){
async == true ? this.option.async = true : this.option.async = false;
}

// 将 post get 转换为大写
this.option.method = method.toUpperCase();

//post 和 get 数据初始化
if(this.option.method != ‘FORMDATA’){// [1]
let data = this.option.data;
if(typeof data === ‘object’){//[2]
if(this.option.method === ‘GET’){
let arr=[];
for(let name in data){
arr.push(`${name}=${data[name]}`);//[3]
}
let strData=arr.join(‘&’);//[4]
this.option.data=`?${strData}`;//[5]
}else if(this.option.method === ‘POST’){
let formData = new FormData();//[6]
for(let key in data){
formData.append(`${key}`,`${data[key]}`);
}
this.option.data=formData;
}

}else if(typeof data === ‘string’ && this.option.method === ‘GET’){//[7]
this.option.data=`?${data}`;
}
}
};
这里详细说说对需要发送数据的处理, 按照序号来说

判断它不是 formData,也就是说是 GET 和 POST 时我们进行数据处理,是 formData 不进行处理,直接发送,这是为了能够实现文件上传功能
判断它是不是 {key:vlue} 这种格式的,是的话解析或拼接,不是的话跳到 [7] 如果是字符串直接加到 url 后边
[3] [4] [5] 这里是为了处理成 url?key=value$key=value 这种 url 传参的数据格式
[6] 是新建了一个 FormData 对象,是 ajax2.0 里边的,它最主要的可以用 ajax 实现文件上传功能,在这里是为了代码简单

打开连接
经过之前的数据处理这里只需要判断下是 GET 还是其他方式(post formdata),然后选择对应的连接方式
open(){
let {method,url,async,data} = this.option;
if(method === ‘GET’){
this.xhr.open(method,url+data,async);
}else{
this.xhr.open(method,url,async);
}
}
设置自定义请求头
将传入的参数进行解析,然后设置自定义请求头代码如下
setHeaders(){
let headers = this.option.headers;
for(let key in headers){
this.xhr.setRequestHeader(`${key.toString()}`,`${headers[key].toString()}`)
}
}

设置返回数据格式、发送数据

由于同步请求时不能设置返回数据格式,所以做下判断
发送数据这里,在经过之前的数据处理后只有 GET 方式有所区别,其他两种没有区别(支持 GET POST 以及我自己定义的一种,更多请求方法可自行扩展)

setResponseType() {
if (this.option.async) {
this.xhr.responseType = this.option.resType;
}
}

sendData(){
if(this.option.method == ‘GET’){
this.xhr.send();
}else{
this.xhr.send(this.option.data);
}
}
请求完成后的数据返回
请求完成后会返回数据 判断 success 以及 error 是不是函数,是的话会将数据返回给 success 或者将错误信息返回给 error
responseData(){
this.xhr.onload = ()=>{
if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){
typeof this.option.success === ‘function’ && this.option.success(this.xhr.response);

}else{
typeof this.option.error === ‘function’ && this.option.error(this.xhr.statusText);

}
}
}
在实现基本功能后,突然想到 jQuery 的 ajax 是会返回一个 promise 对象,可以同时使用回掉函数,或者使用 then 和 catch 来处理数据 因此修改了下传入参数,以及返回数据的处理
传参时代码如下
//add resolve reject
class AJAX {
constructor({url = “”,method = “GET”,data = {},async = true,success,error,resType = “”,headers = {},resolve,reject}) {
this.option = {url,method,data,async,success,error,resType,headers,resolve,reject};
this.xhr = new XMLHttpRequest();
this.start();
}
}
返回数据时代码如下
responseData(){
this.xhr.onload = ()=>{
if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){
typeof this.option.success === ‘function’ && this.option.success(this.xhr.response);
this.option.resolve(this.xhr.response);//add
}else{
typeof this.option.error === ‘function’ && this.option.error(this.xhr.statusText);
this.option.reject(this.xhr.statusText);//add
}
}
}
最终代码
class AJAX {
constructor({url = “”,method = “GET”,data = {},async = true,success,error,resType = “”,headers = {},resolve,reject}) {
this.option = {url,method,data,async,success,error,resType,headers,resolve,reject};
this.xhr = new XMLHttpRequest();
this.start();
}
start() {
// 数据校验
this.checkOption();
// 数据格式的统一
this.initOption();
// 建立连接
this.open();
// 设置请求头
this.setHeaders();
// 设置返回数据格式
this.setResponseType();
// 发送数据
this.sendData()
// 返回成功或失败
this.responseData();
};
checkOption() {
let {url,async,resType,headers} = this.option;
if (url === ”) {
throw new Error(‘ 请求地址不能为空 ’); // 打印错误信息,并停止当前进程
//Console.error(‘ 请求地址为空 ’); 也可以打印错误信息,但是不能停止当前进程
}

if(typeof headers !== ‘object’){
throw new Error(‘ 设置请求头时请传入 {key:value,key:value…} 的格式 ’);
}

if(typeof resType !== ‘string’){
throw new Error(‘ 设置返回数据格式时请传入字符出串格式 ’);
}
// “” 与设置为 ”text” 相同,是默认类型(实际上是 DOMString)
// “arraybuffer” 将接收到的数据类型视为一个包含二进制数据的 JavaScript ArrayBuffer
// “blob” 将接收到的数据类型视为一个包含二进制数据的 Blob 对象
// “document” 将接收到的数据类型视为一个 HTML Document 或 XML XMLDocument,这取决于接收到的数据的 MIME 类型
// “json” 将接收到的数据类型视为 JSON 解析得到的
// “text” 将接收到的数据类型视为包含在 DOMString 对象中的文本
if (typeof url !== ‘string’) {
// 输出警告信息
console.warn(‘ 当前请求地址不是字符串,现在将其尝试转换为字符串 ’);
}
if (async === false && resType != ”) {
console.warn(‘ 如果设置了请求方式为同步,即使设置了返回数据格式也不会生效 ’);
}
};

initOption() {
let {url,async,method} = this.option;
//url 不是字符串转换成字符串
if (typeof url !== ‘string’) {
try {
this.option.url = url.toString();
console.log(` 转换成功: “${this.option.url}”`);
} catch (error) {
throw new Error(‘url 转换字符串失败 ’);
}
}
//async 不是布尔型转成布尔型
if(typeof async !==’boolean’){
async == true ? this.option.async = true : this.option.async = false;
}

// 将 post get 转换为大写
this.option.method = method.toUpperCase();

//post 和 get 数据初始化
if(this.option.method != ‘FORMDATA’){
let data = this.option.data;
if(typeof data === ‘object’){
if(this.option.method === ‘GET’){
let arr=[];
for(let name in data){
arr.push(`${name}=${data[name]}`);
}
let strData=arr.join(‘&’);
this.option.data=`?${strData}`;
}else if(this.option.method === ‘POST’){
let formData = new FormData();
for(let key in data){
formData.append(`${key}`,`${data[key]}`);
}
this.option.data=formData;
}

}else if(typeof data === ‘string’ && this.option.method === ‘GET’){
this.option.data=`?${data}`;
}
}
};

open(){
let {method,url,async,data} = this.option;
if(method === ‘GET’){
this.xhr.open(method,url+data,async);
}else{
this.xhr.open(method,url,async);
}
}

setHeaders(){
let headers = this.option.headers;
for(let key in headers){
this.xhr.setRequestHeader(`${key.toString()}`,`${headers[key].toString()}`)
}
}

setResponseType() {
if (this.option.async) {
this.xhr.responseType = this.option.resType;
}
}

sendData(){
if(this.option.method == ‘GET’){
this.xhr.send();
}else{
this.xhr.send(this.option.data);
}
}

responseData(){
this.xhr.onload = ()=>{
if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){
typeof this.option.success === ‘function’ && this.option.success(this.xhr.response);
this.option.resolve(this.xhr.response);
}else{
typeof this.option.error === ‘function’ && this.option.error(this.xhr.statusText);
this.option.reject(this.xhr.statusText);
}
}
}

all(promises) {
return Promise.all(promises);
};
}

function ajax({url,method,data,async,success,error,resType,headers}){
return new Promise((resolve, reject) => {
return new AJAX({url,method,data,async,success,error,resType,headers,resolve,reject});
});
}
使用时可以将代码复制粘贴到单独的 js 文件然后用 script 标签引入 也可以添加一行 export 代码将最后的 ajax 暴露出去 使用 import 引入
具体使用方法用法
ajax({
url:’api/login’,
method:’post’,// 支持 GET POST 和我自定义的 FORMDATA,传入时不区分大小写
data = {
name:”yhtx”,
id:”1997″
},// 除了这种还支持字符串 “name=yhtx&id=1997″;以及 formData 数据,在传入 formData 数据时请将 method 设置为 FORMDATA
async = true,// 可以使用数字 1 代替 true,数字 0 代替 false
success(res){
// 可以使用回调的形式处理数据也可以使用 then
},error(err){
// 可以使用回调的形式处理错误也可以使用 catch
},
resType = “”,// 可以传入 “” “arraybuffer” “blob” “document” “json” “text”
headers = {
mycookie: “46afqwiocibQEIJfa498./&678” // 使用对象的方式传参
}
}).then((res)=>{
// 可以使用 then 的形式处理数据也可以使用回调函数
}).catch((err)=>{
// 可以使用 catch 的形式处理数据也可以使用回调函数
})

退出移动版