特点
- 基于xhr(node)+Promise的异步ajax请求库
- 浏览器或者node都能使用
- 支持请求/响应拦截器
- 支持取消请求
axios.create(config)
- 根据配置返回一个新的axios
- 新返回来的axios没有取消请求合批量发送请求的方法,其他语法一致
拦截器调用顺序
- 说明:调用axios()并不是立即发送请求的,而是经历一个较长的流程
- 流程:请求拦截器2->请求拦截器1->发送ajax请求->响应拦截器1->响应拦截器2->请求回调
- 流程是使用Promise串联起来的,请求拦截器传递的是config,响应拦截器传递的是response
取消请求
- 配置cancelToken对象
- 缓存用来取消请求的cancel函数
- 在需要的时候调用cancel函数取消请求
- 内部会让cancelPromise变为成功,并且成功值为一个Cancel对象
- 在cancelPromise的成功回调中中断请求,并让发送请求的Promise失败
axios与Axios关系
- 语法上:axios不是Axios实例
- 功能上:axios是Axios实例
- axios是Axios.prototype.request函数bind()返回的函数
- axios作为对象有Axios原型对象上的所有方法,有Axios对象上所有属性
instance与axios的区别
- 相同
- 都是能否发送任意请求的函数:request(config)
- 都能发送特定请求的方法:get()/post()
- 都有默认配置和拦截器属性:default/interceptors
- 不同
- 默认配置可能不一样
- instance没有axios后面添加的一些方法:create()/CancelToken()/all()
请求/响应数据转换器是什么
- 请求转换器是对请求头和请求体数据进行特定处理的函数
- 响应转换器是将响应体json字符串解析为js对象或者数组的函数
axios 对象创建过程代码
- 创建一个
Axios
实例 - 利用
bind
方法将Axios.prototype.request
函数中的this
绑定为创建出来的实例,并返回一个新的函数 - 将
Axios.prototype
中的方法挂载到新返回来的函数中,同时需要绑定this
为创建出来的实例 - 将实例中的属性挂载到新返回来的函数中
function Axios(defaultConfig) {
this.defaultConfig = defaultConfig;
this.interceptor = {
request: {},
respond: {}
};
}
Axios.prototype.request = function(config = {}) {
console.log("发送请求:" + config.method + "请求");
};
Axios.prototype.get = function(config = {}) {
return this.request({
method: "GET",
...config
});
};
Axios.prototype.post = function(config = {}) {
return this.request({
method: "POST",
...config
});
};
function createInstance(config) {
const context = new Axios(config);
const instance = Axios.prototype.request.bind(context);
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context);
});
Object.keys(context).forEach(key => {
instance[key] = context[key];
});
return instance;
}
const axios = createInstance();
axios 请求过程代码
dispatchRequest
函数选择合适的适配器去发送请求,web端或者node端
function Axios(defaultConfig) {
this.defaultConfig = defaultConfig;
}
Axios.prototype.request = function(config = {}) {
console.log("发送请求:" + config.method + "请求");
const promise = Promise.resolve(config);
const chain = [dispatchRequest, undefined];
const result = promise.then(chain[0], chain[1]);
return result;
};
function dispatchRequest(config) {
return xhrAdapter(config)
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(config.method.toUpperCase(), config.url);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
config,
data: xhr.responseText,
headers: xhr.getAllResponseHeaders(),
request: xhr,
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error(`请求失败,失败状态码为${xhr.status}`));
}
}
};
});
}
function createInstance(config) {
const context = new Axios(config);
const instance = Axios.prototype.request.bind(context);
return instance;
}
const axios = createInstance();
axios({
method: "get",
url: "xxxxx"
})
.then(res => {
console.log("请求成功", res);
})
.catch(error => {
console.log("请求失败", error);
});
axios 拦截器代码
InterceptorManage
拦截器管理类有一个use
方法用来收集拦截器Axios.prototype.request
中有一个chain
调用链- 请求的时候,将请求拦截器放置
chain
前面,将相应拦截器放置chain
后面,所以请求拦截器后面的先执行,响应拦截器前面的先执行 - 利用
promise
链式调用chain
中的方法
function InterceptorManage() {
this.handlers = [];
}
InterceptorManage.prototype.use = function(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
});
};
function Axios(defaultConfig) {
this.defaultConfig = defaultConfig;
this.interceptor = {
request: new InterceptorManage(),
response: new InterceptorManage()
};
}
Axios.prototype.request = function(config = {}) {
let promise = Promise.resolve(config);
const chain = [dispatchRequest, undefined];
this.interceptor.request.handlers.forEach(handler => {
chain.unshift(handler.fulfilled, handler.rejected);
});
this.interceptor.response.handlers.forEach(handler => {
chain.push(handler.fulfilled, handler.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
axios 取消请求代码
- 新建一个
CancelToken
实例,该实例上面挂载了一个promise实例,同时将resolve
方法暴露给外面 - 将实例传递给
axios
,axios
发送请求的时候回调CancelToken
实例中的promise。 - 外界调用
resolve
方法,就会中断请求
function Axios(defaultConfig) {
this.defaultConfig = defaultConfig;
}
Axios.prototype.request = function(config = {}) {
const promise = Promise.resolve(config);
const chain = [dispatchRequest, undefined];
const result = promise.then(chain[0], chain[1]);
return result;
};
function dispatchRequest(config) {
return xhrAdapter(config)
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(config.method.toUpperCase(), config.url);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
config,
data: xhr.responseText,
headers: xhr.getAllResponseHeaders(),
request: xhr,
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error(`请求失败,失败状态码为${xhr.status}`));
}
}
};
if (config.cancelToken) {
config.cancelToken.promise.then(() => {
xhr.abort();
reject(new Error("请求中断"));
});
}
});
}
function createInstance(config) {
const context = new Axios(config);
const instance = Axios.prototype.request.bind(context);
return instance;
}
const axios = createInstance();
function CancelToken(executor) {
let resolvePromise;
this.promise = new Promise(function(resolve, reject) {
resolvePromise = resolve;
});
executor(function() {
if (resolvePromise) {
resolvePromise();
}
});
}
let cancel;
document.getElementById("emit").addEventListener("click", function() {
const cancelToken = new CancelToken(function(c) {
cancel = c;
});
axios({
method: "get",
url: "xxx",
cancelToken
})
.then(res => {
console.log("请求成功", res);
})
.catch(error => {
console.log("请求失败", error);
});
});
document.getElementById("off").addEventListener("click", function() {
if (cancel) {
cancel();
}
});