前言
AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术,通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
但是如果多个ajax请求有依赖关系的话,就会形成回调地狱。
axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。
简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。
axios的应用
语法:
axios([config])
axios(url,[config])
axios.get/delete/head/options(url,[config])
axios.post/put/patch(url,[data],[config]
后面三种都是快捷写法,指定了 请求地址/请求方式/请求主体内容 这些东西,config无需再次配置了,最后处理的方案还是第一种。
axios({
url: '/getUsers',
method: 'get',
responseType: 'json', // 默认的
data: {
//'a': 1,
//'b': 2,
}
}).then(function (response) {
console.log(response);
console.log(response.data);
}).catch(function (error) {
console.log(error);
})
axios.get('/user/login', {
baseURL: 'http://127.0.0.1:8888'
}).then(response => {
console.log(response.request);
return response.data;
}).then(data => {
console.log('响应主体信息:', data);
});
axios.post('/user/login', {
// 默认会把对象变为JSON字符串传递给服务器 {"account":"zhufengpeixun","password":"xxxxxx"}
account: 'zhufengpeixun',
password: 'xxxxxx'
}, {
baseURL: 'http://127.0.0.1:8888',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
transformRequest: [function (data) {
return Qs.stringify(data);
}]
});
返回的结果都是一个promise实例。
- 成功:从服务器获取到结果,并且HTTP状态码是以2开始的;
- 失败:
- 从服务获取到数据了,但是HTTP状态码不是以2开始的;
- 没有从服务器获取到数据。
返回结果,response 对象:
status:状态码
statusText:状态文本
request:基于new XMLHttpRequest创建的xhr对象
headers:响应头信息
data:响应主体信息
config:发送请求的时候传递的配置信息
request:基于new XMLHttpRequest创建的xhr对象
isAxiosError:true/false 能从服务器获取到结果,只不过状态码不是以2开头的
response:等同于成功返回的response对象,如果没有从服务器获取任何的结果,response是不存在的
配置信息 config
url:''
method:'get'
baseURL:'' 向服务器发送请求的地址是由baseURL+url完成的
transformRequest:[function(data,headers){
...
return data;
}] 针对于POST系列请求,请求主体传递的信息进行格式化处理,发生在发送ajax请求之前
transformResponse:[function (data) {
...
return data;
}] 针对于服务器响应主体中的信息,进行的格式化处理,发生在自己.then/catch之前
headers:{} 设置自定义请求头信息
params:{} GET系列请求问号传递参数的信息(键值对方式存储,也可以是URLSearchParams对象),axios内部默认你是基于paramsSerializer方法中的Qs.stringify方法把params对象变为xxx=xxx&xxx=xxx格式的
data:{} 请求主体传递信息的对象
timeout:0 设置请求超时时间 在这么久的时间内还没有完成数据请求,则触发xhr.ontimeout事件
withCredentials:false 设置定在跨域请求中是否允许携带资源凭证(例如:cookie)
responseType:'json' axios内部会把服务器返回的信息转换为指定格式的数据,支持:'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
onUploadProgress/onDownloadProgress:function(progressEvent){ ... } 监听上传或者下载的进度,用的是xhr.upload.onprogress事件
validateStatus:function (status) {
return status >= 200 && status < 300;
}
真实项目中,大部分post请求,基于请求主体传递给服务器的格式,不期望是默认的json格式字符串,而是需要改为服务器要求的格式,例如:x-www-form-urlencoded,则需要我们统一基于transformRequest处理。
axios.defaults.validateStatus = status => {
// status是才服务器获取的HTTP状态码
return status >= 200 && status < 300;
};
axios.defaults.transformRequest = [data => {
// data是基于请求主体传递给服务器的信息,transformRequest只对post系列请求有用
// 返回的是啥,最后基于请求主体传递给服务器的就是啥
return Qs.stringify(data);
}];
axios.defaults.transformResponse = [data => {
// data是从服务器获取的响应主体信息,并且根据responseType的值,格式化处理过了
// 返回的是啥,以后基于.then获取的response.data就是啥
try {
data = JSON.parse(data);
} catch (err) {}
return data;
}]
//还有其他默认配置
axios.defaults.baseURL = 'http://127.0.0.1:8888';
axios.defaults.withCredentials = true;
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
// axios.defaults.headers.common/post/get/delete/...
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
前后端数据通信的数据格式
前后端数据通信的数据格式(POST->请求主体 GET->URL问号传递参数):
form-data MIME:multipart/form-data
表单提交
文件上传:文件流信息放置在formData中
x-www-form-urlencoded MIME:application/x-www-form-urlencoded
普通数据的传输一般都用这种方式(这样GET和POST传递给服务器的数据格式统一了)
'xxx=xxx&xxx=xxx'
GET系列请求,URL传递的参数信息其实就是这种格式
raw 原始格式
json字符串 MIME:application/json 服务器返回给客户端的数据一般也都是json格式字符串
text普通字符串 MIME:text/plain
xml字符串 MIME:application/xml
...
binary 文件流
根据上传的文件不同,传递的MIME也是不一样的 例如:image/jpeg 或者 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
...
拦截器:axios.interceptors
请求拦截器:axios.interceptors.request
发生在axios内部帮我们整理好配置项,在发送给服务器之前,请求拦截器一般是对配置项的处理
axios.interceptors.request.use(config => {
// config整理好的配置项,返回全新的config
// 实战场景,例如:每一次向服务器发送请求的时候,可能需要传递一个Token来校验身份(放在请求头中)
let token = localStorage.getItem('token');
if (token) {
// X-Token 服务器要求的名字 ,还可能会叫做Authorization...
config.headers['X-Token'] = token;
}
return config;
});
响应拦截器:axios.interceptors.response
从服务器获取到信息后(或者知道结果,哪怕是失败),到我们自己执行.then/.catch之间触发的
axios.interceptors.response.use(response => {
// 成功:根据validateStatus处理的
// 实际开发中,response包含的信息太多了,但是到业务逻辑层,往往只需要响应主体的信息
return response.data;
}, reason => {
// 失败:获取数据了,但是状态码不对,或者是没有获取任何的数据...
// 实际开发中,不论哪一个请求失败,基本上的提示信息或者处理方案是一致的,此时我们在响应拦截器中对错误进行统一的处理
let response = reason.response;
if (response) {
// 从服务器获取到数据了,只是状态码不对,根据不同的状态码,做不同的提示即可
switch (response.status) {
case 400:
break;
case 401:
break;
case 404:
break;
}
} else {
// 数据都没有获取到
if (!navigator.onLine) {
// 断网了
}
}
return Promise.reject(reason);
});
真实项目配置
真实项目中,根据业务场景上的一写统一情况,还可以封装一个get/post公共方法,方法中往往夹杂着业务的一些统一逻辑,以后基于自己封装的方法发送请求。
function api_get(url, params) {
// ... 自己根据业务逻辑的统一处理
return axios.get(url, {
params
}).then(data => {
if (data.code == 0) {
// 业务成功
return data;
}
// 业务失败
return Promise.reject(data);
});
}
function api_post(url, data) {
// ...
return axios.post(url, data);
}
使用:
axios.get('/user/list').then(data => {
// 此处拿到的直接是基于拦截器处理后的“响应主体”信息
console.log(data);
}).catch(reason => {
});
手写一个简单的axios库
手写的axios虽然没有实现原版axios的全部功能,但是基本符合axios用promise管理ajax的思想。
(function () {
/* 校验是否为浏览器环境 */
if (typeof window === "undefined" || window.window !== window) {
throw new Error('The current environment is not a browser environment, please ensure the correctness of the environment!');
}
/* 创建AJAX请求类 */
class MyAJAX {
constructor(options = {}) {
this.options = options;
this.isGET = /^(GET|HEAD|DELETE|OPTIONS)$/i.test(options.method);
return this.init();
}
init() {
// 请求拦截器是重构CONFIG配置项
let interceptorsRQ = _ajax.interceptors.request[0],
interceptorsRS = _ajax.interceptors.response;
if (typeof interceptorsRQ === "function") {
this.options = interceptorsRQ(this.options);
}
let {
method,
headers,
timeout,
withCredentials,
responseType,
validateStatus
} = this.options;
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest;
xhr.open(method, this.handleURL());
// 设置自定义请求头信息
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
// 其它的配置信息
xhr.withCredentials = withCredentials;
timeout > 0 ? xhr.timeout = timeout : null;
xhr.onreadystatechange = () => {
// 校验网络状态码
let status = xhr.status,
success = status === 200 ? true : false;
typeof validateStatus === "function" ? success = validateStatus(status) : null;
// 网络状态码层面上是成功的
if (success) {
if (xhr.readyState === 4) {
resolve(this.queryInfo(0, xhr));
}
return;
}
// 失败的
reject(this.queryInfo(1, xhr));
};
// 请求超时或者请求中断都算失败的
xhr.ontimeout = () => {
reject(this.queryInfo(1, xhr));
};
xhr.onabort = () => {
reject(this.queryInfo(1, xhr));
};
xhr.send(this.handleBODY());
}).then(...interceptorsRS);
}
// 解析URL
handleURL() {
let {
url,
baseURL,
params
} = this.options;
url = baseURL + url;
// GET请求需要把PARAMS拼接到URL的末尾(和AXIOS不一样的地方,POST请求下我们不处理PARAMS【相对比较标准的】)
if (this.isGET && params) {
if (typeof params === "object") {
// 如果是一个对象,变为URLENCODED格式
let str = ``;
Object.keys(params).forEach(key => {
// 我们不对某一项是对象做处理
str += `&${key}=${params[key]}`;
});
params = str.substring(1);
}
url += `${url.includes('?')?'&':'?'}${params}`;
}
return url;
}
// 解析请求主体
handleBODY() {
let {
data,
transformRequest
} = this.options;
// POST请求下才对DATA做处理
if (!this.isGET && data) {
if (typeof transformRequest === "function") {
data = transformRequest(data);
}
typeof data === "object" ? data = JSON.stringify(data) : null;
return data;
}
return null;
}
// 获取失败/成功信息
queryInfo(lx, xhr) {
let {
responseType
} = this.options;
let response = {
status: xhr.status,
statusText: xhr.statusText,
data: {},
headers: {},
config: this.options,
request: xhr
};
// 获取响应头信息
let allHeaders = xhr.getAllResponseHeaders();
allHeaders = allHeaders.split(/(?:\n)/);
allHeaders.forEach(item => {
if (!item) return;
let [key, value] = item.split(': ');
response.headers[key] = value;
});
if (lx === 0) {
// 把从服务器获取的结果变为responseType指定的数据格式
switch (responseType.toLowerCase()) {
case 'json':
response.data = JSON.parse(xhr.responseText);
break;
case 'text':
response.data = xhr.responseText;
break;
case 'document':
response.data = xhr.responseXML;
break;
case 'arraybuffer':
case 'blob':
case 'stream':
response.data = xhr.response;
break;
default:
response.data = xhr.responseText;
}
return response;
}
// 失败
return {
config: this.options,
request: xhr,
message: xhr.responseText,
response
};
}
}
/* 提供供外面调用的_ajax方法 */
// 把用户传递的配置项和默认配置项进行合并处理
function _initDefaults(options) {
// HEADERS的特殊处理
let headers = options.headers;
if (headers && typeof headers === "object") {
_ajax.defaults.headers = Object.assign(
_ajax.defaults.headers,
headers
);
delete options.headers;
}
return Object.assign(_ajax.defaults, options);
}
function _ajax(options = {}) {
options = _initDefaults(options);
return new MyAJAX(options);
}
// 默认的配置项
_ajax.defaults = {
url: '',
baseURL: '',
method: 'get',
// GET系列请求传递参数
params: {},
// POST系列请求:配置默认传递给服务器的数据格式是JSON格式
data: {},
transformRequest: data => {
if (data !== null && typeof data === "object") {
return JSON.stringify(data);
}
return data;
},
// 自定义请求头信息
headers: {
"Content-Type": "application/json;charset=UTF-8"
},
timeout: 0,
withCredentials: false,
responseType: 'json',
validateStatus: status => (status >= 200 && status < 300)
};
// 还需要提供一些快捷方法 _ajax.get/post/all/interceptors...
["get", "delete", "head", "options"].forEach(name => {
_ajax[name] = function (url, options = {}) {
options.method = name;
options.url = url;
options = _initDefaults(options);
return new MyAJAX(options);
};
});
["post", "put"].forEach(name => {
_ajax[name] = function (url, data = {}, options = {}) {
options.method = name;
options.url = url;
options.data = data;
options = _initDefaults(options);
return new MyAJAX(options);
};
});
// all方法
_ajax.all = function all(arr = []) {
return Promise.all(arr);
};
// 拦截器
function _use(...funcs) {
// this : request/response
// 如果没有传递函数(成功/失败处理),我们给一个默认值
function interceptorsA(response) {
// response:对于请求拦截器来讲就是config
return response;
}
function interceptorsB(reason) {
return Promise.reject(reason);
}
funcs.length === 0 ? funcs = [interceptorsA, interceptorsB] : null;
funcs.length === 1 ? funcs = [funcs[0], interceptorsB] : null;
this.push(...funcs);
}
_ajax.interceptors = {
request: [],
response: []
};
_ajax.interceptors.request.use = _use;
_ajax.interceptors.response.use = _use;
// 暴露到全局供其使用
window._ajax = _ajax;
// 想让其支持ES6Module模块导入(基于CommonJS导出,也可以基于ES6Module导入)
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = _ajax;
}
})();