这篇博客针对axios库的核心代码做一个简要总结
一、关键步骤
1.创建axios对象
axios库导出的对象是一个已经被创建好的axios对象,它本质上是一个方法,可以直接接收一个config配置参数进行请求。在库的入口处,即可看到如下代码:
function createInstance(defaultConfig) {// 传入默认配置生成axios对象const context = new Axios(defaultConfig);// 本质是request方法,this上下文绑定contextconst instance = bind(Axios.prototype.request, context);// 将Axios原型上的属性复制到instanceutils.extend(instance, Axios.prototype, context, {allOwnKeys: true});// 将context上的属性复制到instanceutils.extend(instance, context, {allOwnKeys: true});// 暴露create工厂方法供外部创建自定义的axios对象instance.create = function create(instanceConfig) {return createInstance(mergeConfig(defaultConfig, instanceConfig));};return instance;
}
// 默认暴露的axios对象
const axios = createInstance(defaults);
上述代码是创建默认axios对象的流程,这个对象上有Axios类的实例属性以及原型上的方法,且自身的是一个request方法,整个库最核心的也就是这个request方法。
2.请求
Axios的原型上存在很多辅助方法,如get,post,put等等,这些方法最终都会调用request方法,且默认设置了请求参数中的method属性,仅仅是一种封装的快捷调用方式。所以直接使用axios方法(axios的本质是一个方法),传入一个合法参数和使用axios.get等方法进行请求没有区别。
二、Axios类
1.基础属性
axios类型的对象,存在2个基础属性
- defaults:默认的请求参数
- interceptors:请求和响应的拦截器
2.辅助方法
axios类型的对象存在很多辅助方法,如下:
- delete
- get
- head
- options
- post
- put
- patch
- postForm
- patchForm
- putForm
这些方法本质上都是request的封装,调用这些方法时,都会调用request方法,但相应的会预设置部分请求参数,如method和headers
3.request方法
方法定义如下:
/** * Dispatch a request * * @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults) * @param {?Object} config * * @returns {Promise} The Promise to be fulfilled */request(configOrUrl, config) {}
这个方法是整个库的核心,它接收2个参数作为配置,并且返回一个Promise对象。内部流程如下:
(1)参数合并
if (typeof configOrUrl === 'string') {config = config || {};config.url = configOrUrl;
} else {config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
axios对象在创建时,会传入一个默认请求配置对象,这个对象会和请求时传入的请求配置进行合并处理。
(2)处理headers
// Set config.method
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
// Flatten headers 将共用headers和特定请求的headers进行合并
const defaultHeaders = config.headers && utils.merge(config.headers.common,config.headers[config.method]
);
// 移除掉特定请求相关的header配置,这些配置的用途的限定类型请求带上特定的header,在网络中无用
defaultHeaders && utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],function cleanHeaderConfig(method) {delete config.headers[method];}
);
// 合并header
config.headers = new AxiosHeaders(config.headers, defaultHeaders);
合并后的对象需要进一步处理headers,一些headers配置只针对特定类型的请求,如get类型。
(3)运行执行队列
// 声明请求拦截器队列
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true; //同步执行标识
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {return;}// 只要有一个拦截器配置上是异步模式,则整个流程异步synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;// 在定义拦截器时,会传入2个方法,一个是正常处理的回调,一个是异常时的回调,从对头放入拦截器队列// 注意此时是循环中,用的unshift,因此后定义的在前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 < len) {// 一个promise对象接受2个方法,一个resolve处理正常逻辑,一个reject处理异常逻辑// 这儿将调用方法两两一组,就组成了一个promise的调用链promise = promise.then(chain[i++], chain[i++]);}// 返回最后的那个promisereturn promise;
}
// 下面是同步模式
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
// 这儿本质和异步模式逻辑一样,都是两两一组,但promise的链式调用可以自动捕获异常状态的promise
// 这儿没有链式调用,则使用一个外包的try catch来处理这个异常
// 需要注意同步模式仅限于请求拦截器队列和发送请求方法
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++]);
}
// 返回最终promise
return promise;
这一步是整个request设计的较为精妙的地方,注释已经比较详细,不在阐述。执行队列有2种执行模式,同步模式和异步模式,默认为异步模式。分别如下:
- 异步模式
在异步模式下,细看代码,可以发现链式调用从promise = Promise.resolve(config);开始,并且config参数在请求拦截器的每一个调用环节都需要返回,作为下一个调用环节的入参,因为请求方法需要这个参数。如图所示,这就是一个Promise对象的链式调用,但请求方法所在的Promise没有reject回调,这在代码中是可以体现的,如下:
const chain = [dispatchRequest.bind(this), undefined];
其原因在于最后一个请求拦截器如果返回异常的Promise,则不请求。但由于代码没做容错处理,这儿会报出一个异常Promise没被捕获的错误。
- 同步模式
同步模式和异步模式逻辑上相同,只是实现方式上有点差距,这儿就不在阐述了,同步模式本来就不是常用模式。
简单来说,就是request拦截器,request请求,response拦截器一起组成了一个执行队列。在异步模式下,队列里面每一个执行方法执行完成之后,会继续调用下一个执行方法。同步模式下,request拦截器和request请求会按顺序同步执行,但response拦截器会在请求返回后异步执行。本质上2种模式在调用的时间顺序上是一致的,这个异步和同步只在代码层面上的实现有所差别,这儿不是很理解为什么要设计一个同步模式,并且2种模式在请求有异常的情况下,其处理方式也有差别:
- 异步模式:请求异常时,会被第一个返回拦截器的reject方法捕获,继续执行后续返回拦截器队列
- 同步模式:请求异常时,直接返回一个异常的promise对象,不会执行返回拦截器队列
三、adpter适配器
Axios库是跨平台的,可以在node环境和web环境同时使用。因此内部有个"适配器"的概念,adpter是一个方法,一个请求本质上就是调用这个方法,方法必须返回一个Promise对象。请求配置中,甚至可以让我们自己去实现自己的适配器。
// `adapter` 允许自定义处理请求,这使测试更加容易。 // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
adapter: function (config) { /* ... */ },
如果提供了这么一个参数,那么将不会采用默认的适配器,直接使用提供的适配器。在上述执行队列中有一步是发送请求,这个适配器的调用就是在那个请求方法中。
1.xhradpter
xhradpter适配器适用于web环境,利用XMLHttpRequest对象实现。本质上就是在操作一个XMLHttpRequest对象。
2.httpadpter
httpadpter适配器适用于node环境,利用node原生的http模块实现。
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享