基础知识
1.请求体参数格式,在请求头中设置。
Content-Type: application/x-www-form-urlencoded;charset=utf-8
//用于键值对参数,参数的键值用=连接, 参数之间用&连接 例如: name=%E5%B0%8F%E6%98%8E&age=12
Content-Type: application/json;charset=utf-8
//用于json字符串参数 例如: {"name": "%E5%B0%8F%E6%98%8E", "age": 12}
Content-Type: multipart/form-data
//用于文件上传请求
2.http报文
请求报文 重点是格式与参数
行 POST /s?ie=utf-8 HTTP/1.1
头 Host: atguigu.com
Cookie: name=guigu
Content-type: application/x-www-form-urlencoded
User-Agent: chrome 83
空行
体 username=admin&password=admin //get请求中请求体可以为空
响应报文
行 HTTP/1.1 200 OK
头 Content-Type: text/html;charset=utf-8
Content-length: 2048
Content-encoding: gzip
空行
体 <html>
<head> </head>
<body><h1>尚硅谷</h1></body>
</html>
axios定义和基础知识
什么是axios
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios的特点
- 基本promise的异步ajax请求库
- 浏览器端/node端都可以使用
- 支持请求/响应拦截器
- 支持请求取消
- 转换请求数据和响应数据
- 批量发送多个请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
axios请求方式
axios(config): 通用/最本质的发任意类型请求的方式axios(url[, config]): 可以只指定url发get请求axios.request(config): 等同于axios(config)axios.get(url[, config]): 发get请求axios.delete(url[, config]):发delete请求axios.post(url[, data, config]): 发post请求axios.put(url[, data, config]): 发put请求axios.all(promises): 用于批量执行多个异步请求
**注意:**一定要注意上方data和config的位置特别是get请求和post请求在设置请求头是有所不同。如下:
//GET 请求 两个参数:地址 , 配置参数
axios.get('/axios-server', {
params: {id: 100}, //url 参数
headers: {name: 'atguigu',age: 20} //请求头信息
}).then(value => {
console.log(value);
});
//POST 请求 三个参数:地址 , data(请求体参数) , 配置参数
axios.post('/axios-server', {username: 'admin'}, {
params: {vip: 9}, //url
headers: { height: 180} //请求头参数
});
//POST请求 form表单
axios({
url: '/robot/doSaveRobot',
method: 'post',
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
//请求数据
data: this.robotindeft,
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
}).then(value => {console.log(value)})
//或者
const formdataAll = new FormData();
for (let it in this.robotindeft) {
formdataAll.append(it, this.robotindeft[it])
}
axios.post('/robot/doSaveRobot',formdataAll).then(value => {
console.log(value)
if(value.data.resultcode==1){
this.$message(value.data.msg);
}else{
this.$message(value.data.msg);
this.addClose()
}
});
配置
axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器
使用
直接使用(vue)
进入项目,npm安装npm install axios --save
在main.js下引用axios
import axios from 'axios';
Vue.prototype.$axios=axios;
使用
this.$axios.post('/api/students',{
params: { name:"admin", pass:123123}, //参数
}).then(res => { console.log(res) //请求成功后的处理数
}).catch(err => { console.log(err) //请求失败后的处理数
})
二次封装
创建http.js文件(根据需求使用)
import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'
// 创建axios实例
const service = axios.create({
//请求的默认全局配置
baseURL: baseURL,
timeout: 60000, //响应时长可根据业务需求自行设置
responseType: "json",//请求数据类型包括 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
withCredentials: false,//表示跨域请求时是否需要使用凭证,是true的时候,开启withCredentials后,服务器才能拿到你的cookie,当然后端服务器也要设置允许你获取你开启了才有用
headers: {
'Content-type': 'application/json'
}
})
// request拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
response => {
/**
* code为非20000是抛错 可结合自己业务进行修改
*/
const res = response.data
const codeReg = /^20\d+/
if (!codeReg.test(response.status)) {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
// 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
MessageBox.confirm(
'你已被登出,可以取消继续留在该页面,或者重新登录',
'确定登出',
{
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
store.dispatch('FedLogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
})
}
return Promise.reject('error')
} else {
return response.data
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
源码分析
axios 与 Axios 的关系?
在源码中有axios 与 Axios ,我们平时调用的是axios,而 Axios是一个构造函数。他们的关系是:
- 从语法上来说: axios 不是 Axios 的实例 ;
- 从功能上来说: axios 是 Axios 的实例
- axios 是 Axios.prototype.request 函数 bind()返回的函数
- axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios 对象上所有属性
解释:在axios中有下面一段代码;axios是函数createInstance的执行结果,函数createInstance返回的是instance,instance也是一个函数,执行axios函数就是执行instance函数,而instance函数是Axios.prototype.request.bind(context)得来的,其实执行axio函数就相当于执行 Axios.prototype.request 函数,而且改变了this的指向。有 utils.extend(instance, Axios.prototype, context);这样一行代码,他的解释在代码的上方 .utils.extend(instance, context)将context拷贝给instance,而通过var context = new Axios(defaultConfig);知道context是Axios的实例。所有就是将Axios实例对象上的属性拷贝到instance上: defaults和interceptors属性。此时axios有了Axios的属性和原型方法。所以说从功能上来说: axios 是 Axios 的实例,能通过axios点调用get方法;但是axios还是个函数,所有还能作为函数数调用。
function createInstance(defaultConfig) {
/*
创建Axios的实例
原型对象上有一些用来发请求的方法: get()/post()/put()/delete()/request()
自身上有2个重要属性: defaults/interceptors
*/
var context = new Axios(defaultConfig);
// axios和axios.create()对应的就是request函数
// Axios.prototype.request.bind(context)
var instance = bind(Axios.prototype.request, context); // axios
// 将Axios原型对象上的方法拷贝到instance上: request()/get()/post()/put()/delete()
utils.extend(instance, Axios.prototype, context);
// 将Axios实例对象上的属性拷贝到instance上: defaults和interceptors属性
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported
var axios = createInstance(defaults);
下面我们来看看Axios上的request()/get()/post()/put()/delete()方法怎么来的在Axios上有这么一段代码,意思是通过forEach将这几个方法添加到Axios的原型上,而他们最终调用的还是Axios的原型上的request方法。
// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
运行过程
前面说的都是前期准备工作,现在开始看发送请求的过程,执行过程是三个函数request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
1.request(config):
将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来,返回 promise
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合并配置
config = mergeConfig(this.defaults, config);
// 添加method配置, 默认为get
config.method = config.method ? config.method.toLowerCase() : 'get';
/*
创建用于保存请求/响应拦截函数的数组
数组的中间放发送请求的函数
数组的左边放请求拦截器函数(成功/失败)
数组的右边放响应拦截器函数
*/
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
// 后添加的请求拦截器保存在数组的前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 后添加的响应拦截器保存在数组的后面
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
// 返回用来指定我们的onResolved和onRejected的promise
return promise;
};
**解读:**首先定义了一个promise和一个chain数组,在数组中保存着dispatchRequest(执行请求函数), undefined,通过两个forEach循环将请求拦截和响应拦截的成功失败函数保存到chain数组中。最后通过while循环两个两个的取出函数调用,最后返回promise在调用请求是书写的.then执行对应的onResolved和onRejected函数。
//axios内部会将定义的所有请求拦截的回调函数保存在requestInterceptors数组中
requestInterceptors: [{fulfilled1(){}, rejected1(){}}, {fulfilled2(){}, rejected2(){}}]
//axios内部会将定义的所有响应拦截的回调函数保存在responseInterceptors数组中
responseInterceptors: [{fulfilled11(){}, rejected11(){}}, {fulfilled22(){}, rejected22(){}}]
//通过上方源码可以看出forEach循环requestInterceptors数组,取出一项的两个函数(成功和失败)unshift进chai数组中,所有后定义的请求拦截回调会数组最前方先执行。responseInterceptors数组是push进chai数组的最后,所有响应回调顺序执行。
chain: [
fulfilled2, rejected2, fulfilled1, rejected1,
dispatchReqeust, undefined,
fulfilled11, rejected11, fulfilled22, rejected22
]
//下方就是源码中while循环,promise链回调
promise链回调: config
=> (fulfilled2, rejected2) => (fulfilled1, rejected1) // 请求拦截器处理
=> (dispatchReqeust, undefined) // 发请求
=> (fulfilled11, rejected11) => (fulfilled22, rejected22) // 响应拦截器处理
=> (onResolved, onRejected) // axios发请求回调处理
2.dispatchRequest(config):
转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数据. 返回 promise 。
**注意:**请求数据转换中需要知道,在源码的defaults.js文件中有下面一段代码;axios会根据data的类型指定请求头中请求体参数格式类型
// 请求转换器
transformRequest: [function transformRequest(data, headers) {
// 指定headers中更规范的请求头属性名
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
// 如果data是对象, 指定请求体参数格式为json, 并将参数数据对象转换为json
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
**注意:**响应数据转换中需要知道,也是在defaults.js文件中,对响应的字符串类型进行转换为JSON类型,非字符串类型不做处理。
// 响应数据转换器: 解析字符串类型的data数据
transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
3.xhrAdapter(config):
创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据,返回 promise