1
Axios由 Matt Zabriskie 在 2014 年创建,旨在解决基于 Promise 的 HTTP 请求问题,可以作用于node.js 和浏览器中。在服务端它使用原生node.js 的http模块, 在浏览端则使用 XMLHttpRequest。Axios提供了许多高级功能,如拦截请求和响应,转换请求和响应数据,取消请求,自动转换 JSON 数据,客户端支持防止 CSRF/XSRF等。
2
2.1支持promise API
axios.get('/user?ID=12345')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
});
2.2拦截请求和响应
axios的拦截器可以在请求或响应被then或catch处理前拦截它们。
请求拦截器:
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
响应拦截器:
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
如果有多个请求拦截,会先执行最后一个,依次向前面执行。如果有多个响应拦截,会先执行第一个,依次向后执行。如下图所示:
2.3全局配置
可以指定axios的全局配置,它将作用于每个请求。
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
2.4取消请求
一般用于发送重复请求的情况。从v0.22.0 版本开始,Axios 支持fetch API方式——AbortController 取消请求,CancelToken已被弃用。
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// 取消请求
controller.abort()
3
3.1axios()
// 发起一个post请求
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
3.2调用API
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
3.3创建实例
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
4
axios源码结构:
(1)入口文件
// 创建axios实例
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
// 将Axios的实例方法request赋值给instance,目前instance就是request方法
const instance = bind(Axios.prototype.request, context);
// 在instance上添加Axios所有的原型属性,这个做可以使axios既当方法调用,又可当对象调用
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// 在instance上添加context所有的属性
utils.extend(instance, context, null, {allOwnKeys: true});
// 在instance上添加create方法
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 最终要导出的实例
const axios = createInstance(defaults);
// 向外暴露Axios类用于继承(使用较少)
axios.Axios = Axios;
// 中断请求的相关方法
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
// 错误处理
axios.AxiosError = AxiosError;
// 检测是否为axios抛出的错误
axios.isAxiosError = isAxiosError;
export default axios
入口文件的createInstance方法主要做了三件事:
a. 创建instance变量,挂载上request方法。
b. 将Axios所有的原型方法添加到instance上。
c. 将Axios实例context上所有的属性添加到instance。
我们最终使用的axios就是导出的instance变量。
(2)axios核心类
class Axios {
constructor(instanceConfig) {
// axios的配置
this.defaults = instanceConfig;
// 拦截器
this.interceptors = {
request: new InterceptorManager(), // 请求拦截器
response: new InterceptorManager() // 响应拦截器
};
}
// 发送请求的方法
request(configOrUrl, config) {
...
}
}
// 将普通请求(无body数据)挂载到Axios的prototype上
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
// 将有body数据的请求挂载到Axios的prototype上
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
function generateHTTPMethod(isForm) {
return function httpMethod(url, data, config) {
// 最终调用request()
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}
});
export default Axios;
所有的请求方法,最终都会调用request方法,这个方法是axios请求的核心,下面详细看一下request方法:
// 发送请求的方法
request(configOrUrl, config) {
// 判断参数类型,以支持不同的请求形式
if (typeof configOrUrl === 'string') {
// 第一个参数是字符串的情况 axios('url', {})
config = config || {};
config.url = configOrUrl;
} else {
// 传入的是对象
config = configOrUrl || {};
}
// 合并默认配置
config = mergeConfig(this.defaults, config);
// 请求方法转化为小写
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
config.headers = AxiosHeaders.concat(contextHeaders, headers);
// 请求拦截器存储数组
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true;
// 调用拦截器的forEach方法(高阶函数)
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// runWhen条件执行(当拦截器需要条件执行的时候,只需要在use的时候配置好runWhen就行)
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// 标识拦截器是同步还是异步,默认是flase 异步,只有所有请求拦截器都是同步的情况下才会同步进行,
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 响应拦截器存储数组
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
// 异步(默认情况)
if (!synchronousRequestInterceptors) {
// 下面是axios经典的promise链式调用
const chain = [dispatchRequest.bind(this), undefined];
// 将请求拦截器里的方法添加到数组前面
chain.unshift.apply(chain, requestInterceptorChain); // 等同于chain.unshift(...requestInterceptorChain),但扩展运算符是深拷贝,会占用内存空间
// 将响应拦截器里的方法添加到数组后面
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
// 执行chain数组中的函数
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
// 以下是同步的逻辑
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
// 执行请求拦截器
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
// 发送请求
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
i = 0;
len = responseInterceptorChain.length;
// 执行响应拦截器
while (i < len) {
promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
return promise;
}
主要逻辑如下图所示:
(3)拦截器实现
class InterceptorManager {
constructor() {
this.handlers = [];
}
// use方法添加拦截器
use(fulfilled, rejected, options) {
// 将拦截器成功、失败的回调,以及设置的options参数添加到this.handlers数组
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
// 注销指定拦截器
eject(id) {
if (this.handlers[id]) {
// 将数组中该拦截器设置为null
this.handlers[id] = null;
}
}
// 注销所有拦截器
clear() {
if (this.handlers) {
this.handlers = [];
}
}
// 遍历拦截器
forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
(4)dispatchRequest方法
dispatchRequest是axios发送请求的核心方法,返回adapter适配器,adapter会根据当前环境,返回xhr或http方法,最终的请求方法由xhr或http方法执行。
import adapters from "../adapters/adapters.js";
export default function dispatchRequest(config) {
// 适配器模式
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
return adapter(config).then(function onAdapterResolution(response) {
// 转换响应数据
response.data = transformData.call(
config,
config.transformResponse,
response
);
response.headers = AxiosHeaders.from(response.headers);
return response;
}, function onAdapterRejection(reason) {
...
return Promise.reject(reason);
});
}
getAdapter只做了一件事,就是根据不同的环境,使用不同的请求方式,如果用户自定义了adapter就使用config.adapter,否则就使用默认的defaults.adapter,代码如下:
import httpAdapter from './http.js';
import xhrAdapter from './xhr.js';
const knownAdapters = {
http: httpAdapter,
xhr: xhrAdapter
}
export default {
// 参数adapters是用户自定义的参数,可以是xhr或http
getAdapter: (adapters) => {
adapters = utils.isArray(adapters) ? adapters : [adapters];
const {length} = adapters;
let nameOrAdapter;
let adapter;
// 遍历adapters
for (let i = 0; i < length; i++) {
nameOrAdapter = adapters[i];
// 给adapter赋值
if((adapter = utils.isString(nameOrAdapter) ? knownAdapters[nameOrAdapter.toLowerCase()] : nameOrAdapter)) {
break; // 终止循环
}
}
// 最终返回knownAdapters中的xhrAdapter或httpAdapter
return adapter;
}
}
xhrAdapter中封装了原生的ajax请求,httpAdapter中封装了node的http模块,浏览器端用ajax请求,服务端用的是node的http模块。我们日常在浏览器端使用较多,因此着重看一下ajax的封装:
export default isXHRAdapterSupported && function (config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
...
// 创建XMLHttpRequest对象
let request = new XMLHttpRequest();
// HTTP基本认证
if (config.auth) {
const username = config.auth.username || '';
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
}
// 设置请求方式
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// 设置超时时间
request.timeout = config.timeout;
if ('onloadend' in request) {
request.onloadend = onloadend;
} else {
// 监听readyState属性变化
request.onreadystatechange = function handleLoad() {
...
};
}
// 设置请求头
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
request.setRequestHeader(key, val);
});
}
// 指跨域时是否携带cookie,默认为false
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// 如果配置了download或upload,需要监听progress事件,处理上传或下载的应用
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
}
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
}
// 发送请求
request.send(requestData || null);
});
}
以上就是 axios的源码的核心内容,当然还有很多细节没有说到,比如:错误处理,状态码处理等,大家有兴趣的可以自己去细读源码,只有自己阅读一次才能更好的理解 axios的优雅之处。