前端同学们对于axios应该都有所耳闻。但你有没有好奇过,axios是如何实现的呢?源码又是怎样的呢?
让我们一起花十分钟的时间,深入学习
axios 的源码实现!
01:axios 实例与请求流程
在深入源码之前,先简要了解一下axios实例的属性和整体请求流程,这将有助于我们更轻松地阅读源码。
下面是axios实例属性的简图:
axios
实例主要有三个核心属性:
-
config
:包括url、method、params、headers等配置。
-
interceptors
:拦截器,分为请求拦截器和返回拦截器。
-
request
:用于调用xhr或http请求的方法,参数是config。
由于可以再次传入
config
调用
request
方法,但不能传入
interceptors
,因此需要在
axios
上提前添加拦截器,无法临时添加。
以下是
axios
的请求流程图:
02:源码文件结构解析
axios的源码都在
lib
文件夹下,最核心的内容在
core
文件夹里。
lib
│ axios.js // 最终导出的文件
│ utils.js // 工具类
├─adapters // 适配器相关
│ adapters.js //适配器类
│ http.js // node请求
│ xhr.js // 浏览器请求
├─cancel // 取消功能相关
│ CanceledError.js //取消异常类
│ CancelToken.js //取消token类
│ isCancel.js //判断是否取消
├─core // 核心功能相关
│ Axios.js // axios类
│ AxiosError.js // axios异常类
│ AxiosHeaders.js // 请求头
│ buildFullPath.js // 构造请求地址
│ dispatchRequest.js // 发送请求方法
│ InterceptorManager.js // 拦截器的类
│ mergeConfig.js // 合并配置方法
│ settle.js // 处理请求结果方法
│ transformData.js // 数据转换执行方法
├─defaults // 默认配置
│ index.js // 默认请求参数配置
│ transitional.js // 默认transitional配置
├─env // node环境没有FormData,
│ │ data.js
│ └─classes
│ FormData.js
├─helpers // 各种工具类方法,看名字就可以大概猜到作用(太长了进行省略...)
│ ...
└─platform // 为不同环境下准备的方法(太长了进行省略...)
...
03:源码文件阅读
3.1:入口文件 axios.js
该文件创建了
axios
实例并导出,因此我们通过
import axios from 'axios'
引入的即为该实例,无需再
new Axios({...})
。
以下是
axios
实例创建的代码:
// 核心方法,根据config创建axios实例
function createInstance (defaultConfig) {
// 创建axios实例
const context = new Axios(defaultConfig);
// 给Axios原型上的request方法绑定context为它的this
// 这个instance就是我们最终使用的axios
// 没想到吧,最开始的instance其实是个函数,
// 所以我们才可以使用这个用法axios('/api/url')
// 只不过后面给它扩展了很多东西
const instance = bind(Axios.prototype.request, context);
// 将Axios.prototype上的属性都绑定到instance上,
// 这样它就拥有了简写的请求方法,比如axios.get(),axios.post()
// 如果是函数,this绑定为context
utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
// 将context上的属性都绑定到instance上,
// 这样它就拥有了拦截器属性,可以使用axios.interceptors.request.use()
// 因为context上的函数的this本就指向context,所以第三个参数不需要再指定
utils.extend(instance, context, null, { allOwnKeys: true });
// 给instance增加create方法,可以通过create创建一个实例
instance.create = function create (instanceConfig) {
// 入参为拼接配置项,以instanceConfig为优先
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 调用上面的方法,最终导出的是axios,
// 其实是Axios.prototype.request,并扩展了很多属性
const axios = createInstance(defaults);
// 继续给axios增加属性
// 这就说明如果自己通过const myAxios=axios.create({});
// 创建出来的实例就没有下面这些属性了
// 所以下面这些属性只能通过import axios from 'axios';
// axios.all()这样的方式来使用
axios.Axios = Axios;
// Cancel相关
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
// 工具函数,将对象转为FormData
axios.toFormData = toFormData;
// Axios通用异常类
axios.AxiosError = AxiosError;
// Cancel异常类
axios.Cancel = axios.CanceledError;
// Expose all/spread
// 工具函数
axios.all = function all (promises) {
return Promise.all(promises);
};
// 工具函数,利用apply将数组参数改为单个传入的参数
axios.spread = spread;
// 判断异常是否是AxiosError
axios.isAxiosError = isAxiosError;
// 合并config对象方法
axios.mergeConfig = mergeConfig;
axios.AxiosHeaders = AxiosHeaders;
// 工具方法
axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
// 获取适配器:xhr 、http
axios.getAdapter = adapters.getAdapter;
// 请求状态
axios.HttpStatusCode = HttpStatusCode;
axios.default = axios;
// 最终导出
export default axios
3.2:Axios类
Axios类
是 Axios 的核心方法,其中的
request
方法是最核心的请求方法。
以下是
Axios类
的核心代码:
class Axios {
// 可以看到Axios的构造函数相当简单
// 仅仅是保存了我们传入的config,
// 然后初始化空的拦截器对象
constructor(instanceConfig) {
// 所有的配置都设置再defaults上
this.defaults = instanceConfig;
// 初始化空的拦截器对象,包含请求拦截器request和返回拦截器response
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// request是Axios的核心方法
// 所有的核心都在request方法里,
// request方法接收两种参数,【直接传config对象】或者【传url和config对象】
request (configOrUrl, config) {
// 允许axios('example/url'[, config]) 这样使用
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
// request会使用传入的配置merge默认配置
// 所以即使只传了一个url,也会使用默认的Get方法
config = mergeConfig(this.defaults, config);
const { headers } = config;
// 默认get请求
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
// 说明header可以直接设置
// 也可以在common设置通用header,也可以为每种请求设置特定的header
let contextHeaders = headers && utils.merge(
headers.common,
headers[config.method]
);
headers && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
(method) => {
delete headers[method];
}
);
// 优先使用headers下配置,再使用headers.common和headers[get,post]的配置
config.headers = AxiosHeaders.concat(contextHeaders, headers);
// 请求拦截器链
const requestInterceptorChain = [];
// 记录是否使用同步的方式调用,我们配置拦截器的时候,默认是false,也就是异步
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors (interceptor) {
// 如果配置了runWhen函数,那么会先执行runWhen,如果为true,才会添加该拦截器
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// unshift说明后传入的请求拦截器先执行,一次放入两个,分别是fulfilled和rejected
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 响应拦截器链
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors (interceptor) {
// push说明先传入的响应拦截器先执行
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
// 默认是异步执行,也就是一个执行完再执行下一个
if (!synchronousRequestInterceptors) {
//dispatchRequest是真正的发送请求
const chain = [dispatchRequest.bind(this), undefined];
// 前面插入请求拦截器
chain.unshift.apply(chain, requestInterceptorChain);
// 后面插入响应拦截器
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
// 依次执行
while (i promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
// 同步执行,请求拦截器
while (i 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 promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
return promise;
}
// 获取请求地址
getUri (config) {
config = mergeConfig(this.defaults, config);
const fullPath = buildFullPath(config.baseURL, config.url);
return buildURL(fullPath, config.params, config.paramsSerializer);
}
}
// Provide aliases for supported request methods
// 给Axios原型注入四个请求方法,请求方法本质都是调用request方法
// 这四个都是不带请求体的
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
}));
};
});
// 给Axios注入post,put,patch,postForm,putForm,patchForm方法
// 这几个方法都是带请求体的
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData (method) {
function generateHTTPMethod (isForm) {
return function httpMethod (url, data, config) {
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}
Axios.prototype[method] = generateHTTPMethod();
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});
export default Axios;
3.3:InterceptorManager类
拦截器的管理者,可以添加、删除和清空拦截器。
以下是
InterceptorManager
类的代码:
axios.interceptors.request.use({
fulfilled:()=>{},
rejected:()=>{}
})
可以看到我们给use传递的是一个对象,对象包含fulfilled函数和rejected函数。
接下来看源码:
class InterceptorManager {
// 构造函数只初始化了一个空的handlers数组
// 拦截器就是放在这个数组里的
constructor() {
this.handlers = [];
}
// 添加拦截器,返回索引,可以用索引来移除拦截器
// 可以发现除了fulfilled和rejected,
// 我们还可以设置synchronous和runWhen
// runWhen函数用来动态控制是否使用该拦截器
use (fulfilled, rejected, options) {
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]) {
this.handlers[id] = null;
}
}
// 清空拦截器
clear () {
if (this.handlers) {
this.handlers = [];
}
}
// 提供遍历拦截器快捷操作
forEach (fn) {
utils.forEach(this.handlers, function forEachHandler (h) {
if (h !== null) {
fn(h);
}
});
}
}
export default InterceptorManager;
3.4:dispatchRequest发送请求
看完上面的代码,我们已经基本搞清楚了axios的整体流程:
组装config->组装header->调用请求拦截器->发送实际请求->调用返回拦截器。
但是我们还不知道axios具体是如何调用请求的,那么接下来就要看dispatchRequest代码
// 暂且先记住,这个函数的作用就是用来判断请求是否被取消,
// 如果要的话,则直接抛出异常,
function throwIfCancellationRequested (config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new CanceledError(null, config);
}
}
// 发送请求核心函数
export default function dispatchRequest (config) {
// 刚开始请求前判断一次是否取消
throwIfCancellationRequested(config);
config.headers = AxiosHeaders.from(config.headers);
// 执行数据转换操作
config.data = transformData.call(
config,
config.transformRequest
);
// 默认设置请求头的contentType为application/x-www-form-urlencoded
if (['post', 'put', 'patch'].indexOf(config.method) !== -1) {
config.headers.setContentType('application/x-www-form-urlencoded', false);
}
// 获取适配器,如果是浏览器环境获取xhr,
// 如果是Node环境,获取http
// 适配器就是最终用来发送请求的东西
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
// 请求是使用适配器执行config
return adapter(config).then(function onAdapterResolution (response) {
// 请求完之后判断是否要取消
throwIfCancellationRequested(config);
// 对返回结果进行转换
response.data = transformData.call(
config,
config.transformResponse,
response
);
// 设置返回头
response.headers = AxiosHeaders.from(response.headers);
return response;
}, function onAdapterRejection (reason) {
// 如果不是因为取消而报错
if (!isCancel(reason)) {
// 再次判断是否要取消,如果是会抛出异常
throwIfCancellationRequested(config);
// 处理正常错误的返回值
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
config.transformResponse,
reason.response
);
reason.response.headers = AxiosHeaders.from(reason.response.headers);
}
}
return Promise.reject(reason);
});
}