网上虽然有很多ajax的封装,但都或多或少的存在一些问题,比如没有封装取消请求的方法,也没有对请求参数的对象属性是数组或对象的处理。
以jquery为例,这个请求的数据就比较复杂
$.ajax({
type:'get',
data:{
name:'张三',
age:18,
url:'http://172.31.14.33:3000',
info:{
hobbies:['唱','跳','rap','篮球'],
score:{
english:79,
math:90,
}
}
}
})
jquery处理后的url是这样的:

企业微信截图_20221031182317.png

企业微信截图_20221031182327.png
axios的处理是这样的:

企业微信截图_20221031183112.png

企业微信截图_20221031183122.png
最后我采用了axios的封装形式。
Promise形式的封装(使用方式类似于axios)
代码:
//my_ajax_promise.js
let ajax = function (options) {
//步骤:
//1.创建异步请求对象
//2.创建状态监听函数
//3.设置请求方式和地址
//4.发送请求
//5.接收返回的数据
return new Promise((resolve, reject) => {
//1.创建异步请求对象
let xhr = new XMLHttpRequest() || new ActiveXObject('Mricosoft.XMLHTTP');//兼容IE6及以下
//设置超时时间
if (options.timeout > 0) {
xhr.timeout = options.timeout;
}
//处理请求方式
if (options.method) {
options.method = options.method.toUpperCase();//转成大写
} else {
options.method = 'GET';//默认为get请求
}
//2.创建事件监听函数,如果在open之后才创建监听函数,readyState就已经变成2了
//如果根据xhr.readyState去处理的话不好区别错误类型,这里全部使用事件进行处理
bindEvent(xhr, resolve, reject, options);
//3.设置请求方式和地址
if (options.method === 'POST') {//post请求
//设置请求方式,请求地址和是否异步
xhr.open('POST', options.url, options.async ? options.async : true);
//自定义请求头(open之后才能设置请求头)
setRequestHeader(xhr, options);
//4.发送请求
if (options.processData !== false) {//post请求时数据是否需要转字符串,默认要(FormData等数据不用转)
options.processData = true;
}
if (options.processData) {//是否需要转成JSON字符串
xhr.send(JSON.stringify(options.data));
} else {
xhr.send(options.data);
}
} else {//get请求
xhr.open('GET', options.url + '?' + paramsToUrl(options.data), options.async ? options.async : true);
setRequestHeader(xhr, options);
xhr.send();
}
//取消请求
if (options.cancelToken) {
let executor = options.cancelToken;
executor(xhr.abort.bind(xhr));//执行executor函数并传参,让abort的this始终指向其异步对象,避免abort在被调用时其this指向被改变
}
})
}
//CancelToken的实例对象返回executor函数
ajax.CancelToken = function (executor) {
return executor;
}
//绑定事件
function bindEvent(xhr, resolve, reject, options) {
//开始接收事件,在接收到响应数据的第一个字节时触发
xhr.onloadstart = function (e) {}
//上传事件,有文件上传时触发
if (xhr.upload) {
xhr.upload.onprogress = function (e) {
if (typeof options.onUploadProgress === 'function') {
try {
options.onUploadProgress(e);
} catch (err) {
reject(error('TypeError', err,xhr));
throw new TypeError(err);
}
}
}
}
//接收进度事件,在请求接收到数据的时候被周期性触发
xhr.onprogress = function (e) {
if (typeof options.onDownloadProgress === 'function') {
try {
options.onDownloadProgress(e);
} catch (err) {
reject(error('TypeError', err,xhr));
throw new TypeError(err);
}
}
}
//超时事件,请求超时时触发
xhr.ontimeout = function (e) {
reject(error(e.type, 'network timeout',xhr));
}
//取消事件,取消请求时触发
xhr.onabort = function (e) {
reject(error(e.type, 'upload canceled',xhr));
}
//错误事件,在请求发生错误时触发
xhr.onerror = function (err) {
reject(error(err.type, 'network error',xhr));
}
//请求完成的时候会触发load事件
xhr.onload = function (e) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(success(xhr));
} else {
reject(error('error', 'network error',xhr));
}
}
//请求完成事件,loadend事件总是在一个资源的加载进度停止之后被触发(error、abort或load都能触发)
xhr.onloadend = function (e) {
//兜底处理(按理来说不会走到这里,万一呢)
if (xhr.status >= 200 && xhr.status < 300) {
resolve(success(xhr));
} else {
reject(error('error', 'network error',xhr));
}
}
}
//设置请求头
function setRequestHeader(xhr, options) {
//跨域请求时是否需要使用cookie凭证(默认不需要)
if(options.withCredentials){
xhr.withCredentials=true;
}
if (options.headers) {
for (const key in options.headers) {
if (options.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, options.headers[key]);
}
}
//没设置Content-Type时默认为JSON,上传文件时可设置contentType为false,让浏览器根据数据类型自动设置即可
if (!options.headers['Content-Type'] && options.contentType !== false) {
xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
}
} else if (options.contentType !== false) {
xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
}
}
//将get请求数据转成url参数(参数处理格式参考了axios和jquery对get请求参数的处理)
function paramsToUrl(params) {
let _result = [];
for (let key in params) {
if (params.hasOwnProperty(key)) {
let value = params[key]
// 去掉为空的参数
if (['', undefined, null].includes(value)) {
continue;
}
if (Array.isArray(value)) {//数组处理
arrayDeal(_result, key, value);
} else if (Object.prototype.toString.call(value) === '[object Object]') {//对象处理
objDeal(_result, key, value);
} else {
_result.push(key + '=' + encodeURIComponent(value));
}
}
}
return _result.length ? _result.join('&') : ''
}
//数组遍历并处理
function arrayDeal(result, attributeName, array) {
array.forEach(function (item, index) {
if (Array.isArray(item)) {//数组元素是数组的话需要进行遍历
arrayDeal(result, attributeName + '[' + index + ']', item);
} else if (Object.prototype.toString.call(item) === '[object Object]') {//数组元素是对象的话需要进行处理
objDeal(result, attributeName + '[' + index + ']', item);
} else {
result.push(attributeName + '[' + index + ']=' + encodeURIComponent(item));
}
})
}
//对象属性遍历并处理
function objDeal(result, attributeName, obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (Array.isArray(obj[key])) {//对象属性值为数组的时候需要进行处理
arrayDeal(result, attributeName + '[' + key + ']', obj[key]);
} else if (Object.prototype.toString.call(obj[key]) === '[object Object]') {//对象属性值为对象时需要进行处理
objDeal(result, attributeName + '[' + key + ']', obj[key]);
} else {
result.push(attributeName + '[' + key + ']=' + encodeURIComponent(obj[key]));
}
}
}
}
//错误对象
function error(type, msg,xhr) {
return {
type: type || '',
errorMsg: msg || '',
request:xhr,
}
}
//成功对象
function success(xhr) {
let responseData = {
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: xhr.status,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: xhr.statusText,
// `headers` 服务器响应的头
headers: xhr.getAllResponseHeaders(),
request: xhr,
}
try {
//接口返回的数据是json字符串时
responseData.data = JSON.parse(xhr.response);
return responseData;
} catch (e) {
responseData.data = xhr.response;
return responseData;
}
}
export default ajax;
基本的使用方式:
import ajax from "./my_ajax_promise.js";
let CancelToken = ajax.CancelToken;
let cancel;//接收取消请求
ajax({
method: '',
url: '',
data: {},
onUploadProgress: function (e) {
console.log(e);
},
onDownloadProgress: function (e) {
console.log(e);
},
cancelToken: new CancelToken(function executor(c) {
// executor的参数c就是取消请求的方法
cancel = c;//调用cancel即可取消请求
})
}).then(res => {
//成功逻辑
}).catch(err => {
//失败逻辑
});
回调形式的封装(使用方式类似于jquery的ajax)
代码:
//my_ajax_callback.js
let ajax = function (options) {
//步骤:
//1.创建异步请求对象
//2.创建状态监听函数
//3.设置请求方式和地址
//4.发送请求
//5.接收返回的数据
//1.创建异步请求对象
let xhr = new XMLHttpRequest() || new ActiveXObject('Mricosoft.XMLHTTP');//兼容IE6及以下
xhr.isEnd=false;//判断请求是否已经被处理完毕,避免重复触发事件
//设置超时时间
if (options.timeout > 0) {
xhr.timeout = options.timeout;
}
//处理请求方式
if (options.method) {
options.method = options.method.toUpperCase();//转成大写
} else {
options.method = 'GET';//默认为get请求
}
//2.创建事件监听函数,如果在open之后才创建监听函数,readyState就已经变成2了
//如果根据xhr.readyState去处理的话不好区别错误类型,这里全部使用事件进行处理
bindEvent(xhr, options);
//3.设置请求方式和地址
if (options.method === 'POST') {//post请求
//设置请求方式,请求地址和是否异步
xhr.open('POST', options.url, options.async ? options.async : true);
//自定义请求头(open之后才能设置请求头)
setRequestHeader(xhr, options);
//4.发送请求
if (options.processData !== false) {//post请求时数据是否需要转字符串,默认要(FormData等数据不用转)
options.processData = true;
}
if (options.processData) {//是否需要转成JSON字符串
xhr.send(JSON.stringify(options.data));
} else {
xhr.send(options.data);
}
} else {//get请求
xhr.open('GET', options.url + '?' + paramsToUrl(options.data), options.async ? options.async : true);
setRequestHeader(xhr, options);
xhr.send();
}
//将xhr对象返回出去,便于在外部调用其方法(例如abort方法)
return xhr;
}
//绑定事件
function bindEvent(xhr, options) {
//开始接收事件,在接收到响应数据的第一个字节时触发
xhr.onloadstart = function (e) {}
//上传事件,有文件上传时触发
if (xhr.upload) {
xhr.upload.onprogress = function (e) {
if (typeof options.onUploadProgress === 'function') {
try {
options.onUploadProgress(e);
} catch (err) {
if(typeof options.error==='function'){
options.error(error('TypeError', err,xhr))
}
throw new TypeError(err);
}
}
}
}
//接收进度事件,在请求接收到数据的时候被周期性触发
xhr.onprogress = function (e) {
if (typeof options.onDownloadProgress === 'function') {
try {
options.onDownloadProgress(e);
} catch (err) {
if(typeof options.error==='function'){
options.error(error('TypeError', err,xhr))
}
throw new TypeError(err);
}
}
}
//超时事件,请求超时时触发
xhr.ontimeout = function (e) {
if(xhr.isEnd){
return;
}else {
xhr.isEnd=true;
}
if(typeof options.error==='function'){
options.error(error(e.type, 'network timeout',xhr));
}
}
//取消事件,取消请求时触发
xhr.onabort = function (e) {
if(xhr.isEnd){
return;
}else {
xhr.isEnd=true;
}
if(typeof options.error==='function'){
options.error(error(e.type, 'upload canceled',xhr));
}
}
//错误事件,在请求发生错误时触发
xhr.onerror = function (err) {
if(xhr.isEnd){
return;
}else {
xhr.isEnd=true;
}
if(typeof options.error==='function'){
options.error(error(err.type, 'network error',xhr));
}
}
//请求完成的时候会触发load事件
xhr.onload = function (e) {
if(xhr.isEnd){
return;
}else {
xhr.isEnd=true;
}
if (xhr.status >= 200 && xhr.status < 300) {
if(typeof options.success==='function'){
options.success(success(xhr))
}
} else {
if(typeof options.error==='function'){
options.error(error('error', 'network error',xhr));
}
}
}
//请求完成事件,loadend事件总是在一个资源的加载进度停止之后被触发(error、abort或load都能触发)
xhr.onloadend = function (e) {
if(xhr.isEnd){
return;
}else {
xhr.isEnd=true;
}
//兜底处理(按理来说不会走到这里,万一呢)
if (xhr.status >= 200 && xhr.status < 300) {
if(typeof options.success==='function'){
options.success(success(xhr));
}
} else {
if(typeof options.error==='function'){
options.error(error('error', 'network error',xhr))
}
}
}
}
//设置请求头
function setRequestHeader(xhr, options) {
//跨域请求时是否需要使用cookie凭证(默认不需要)
if(options.withCredentials){
xhr.withCredentials=true;
}
if (options.headers) {
for (const key in options.headers) {
if (options.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, options.headers[key]);
}
}
//没设置Content-Type时默认为JSON,上传文件时可设置contentType为false,让浏览器根据数据类型自动设置即可
if (!options.headers['Content-Type'] && options.contentType !== false) {
xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
}
} else if (options.contentType !== false) {
xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
}
}
//将get请求数据转成url参数(参数处理格式参考了axios和jquery对get请求参数的处理)
function paramsToUrl(params) {
let _result = [];
for (let key in params) {
if (params.hasOwnProperty(key)) {
let value = params[key]
// 去掉为空的参数
if (['', undefined, null].includes(value)) {
continue;
}
if (Array.isArray(value)) {//数组处理
arrayDeal(_result, key, value);
} else if (Object.prototype.toString.call(value) === '[object Object]') {//对象处理
objDeal(_result, key, value);
} else {
_result.push(key + '=' + encodeURIComponent(value));
}
}
}
return _result.length ? _result.join('&') : ''
}
//数组遍历并处理
function arrayDeal(result, attributeName, array) {
array.forEach(function (item, index) {
if (Array.isArray(item)) {//数组元素是数组的话需要进行遍历
arrayDeal(result, attributeName + '[' + index + ']', item);
} else if (Object.prototype.toString.call(item) === '[object Object]') {//数组元素是对象的话需要进行处理
objDeal(result, attributeName + '[' + index + ']', item);
} else {
result.push(attributeName + '[' + index + ']=' + encodeURIComponent(item));
}
})
}
//对象属性遍历并处理
function objDeal(result, attributeName, obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (Array.isArray(obj[key])) {//对象属性值为数组的时候需要进行处理
arrayDeal(result, attributeName + '[' + key + ']', obj[key]);
} else if (Object.prototype.toString.call(obj[key]) === '[object Object]') {//对象属性值为对象时需要进行处理
objDeal(result, attributeName + '[' + key + ']', obj[key]);
} else {
result.push(attributeName + '[' + key + ']=' + encodeURIComponent(obj[key]));
}
}
}
}
//错误对象
function error(type, msg,xhr) {
return {
type: type || '',
errorMsg: msg || '',
request:xhr,
}
}
//成功对象
function success(xhr) {
let responseData = {
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: xhr.status,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: xhr.statusText,
// `headers` 服务器响应的头
headers: xhr.getAllResponseHeaders(),
request: xhr,
}
try {
//接口返回的数据是json字符串时
responseData.data = JSON.parse(xhr.response);
return responseData;
} catch (e) {
responseData.data = xhr.response;
return responseData;
}
}
基本的使用方式:
import ajax from "./my_ajax_callback.js";
let xhr=ajax({
method:'post',
url:'',
data:{},
success:function (res) {
//处理成功请求
console.log(res);
},
error:function (err) {
//处理失败请求
console.log(err);
}
})
//xhr.abort();//取消请求