You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and node.js with the same codebase). On the server-side it uses the native node.js http module, while on the client (browser) it uses XMLHttpRequests.
classInterceptorManager{constructor(){this.handlers=[];}/** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */// 返回一个id,这个id用于后续eject来去除该interceptoruse(fulfilled,rejected,options){this.handlers.push({
fulfilled,
rejected,synchronous: options ? options.synchronous : false,// synchronous 默认是 false -> 异步runWhen: options ? options.runWhen : null// runWhen 只有针对 requestInterceptor 才会进行检查,要么不输入,如果输入的话需要是一个 function,当 runWhen(config) === true 的时候,才执行该 interceptor});returnthis.handlers.length-1;}/** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` * * @returns {Boolean} `true` if the interceptor was removed, `false` otherwise */eject(id){if(this.handlers[id]){this.handlers[id]=null;}}/** * Clear all interceptors from the stack * * @returns {void} */clear(){if(this.handlers){this.handlers=[];}}/** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor * * @returns {void} */forEach(fn){utils.forEach(this.handlers,functionforEachHandler(h){if(h!==null){fn(h);}});}}
Axios 源码解析
什么是 Axios
Axios 是一个基于 promise 的网络请求库,通过使用它我们可以非常轻松的发起请求。它是 isomorphic 的(也即同一套代码可以同时运行在 node.js 环境和浏览器环境中)。在服务端它使用了 node.js 原生的
http
模块,而在浏览器端则使用了XMLHttpRequest
Why Axios
Axios 本身就支持了非常多强大的功能和特性,同时有着很强的社区支持,其易于使用、易于封装魔改的特性也备受开发人员的青睐。Axios 的主要特性包括
在简单的了解了 Axios 是什么和它的基本特性之后,让我们正式开始了解 Axios 的源码吧~
起点
Axios 的起点在
./lib/axios.js
文件中(https://github.com/axios/axios/blob/v1.x/lib/axios.js),我们可以从这里开始分析。可以看到,在
axios.js
中,其实只做了三件事情:createInstance
方法import axios from 'axios'
时,得到的也是这个刚创建的 Axios 实例。除此以外,我们可以看到,在我们平时开发的时候也可以使用
const myAxios = axios.create(config)
通过自定义配置新建一个实例,而这个方法也是在createInstance
方法中被定义到了 axios 实例上!但是我们发现, 只有
create
这个方法定义在了createInstance
内部,这则改动与这条 PR 有关 axios/axios#2795,可以看到,在之前,我们调用create(customConfig)
创建新的实例的时候,都只能够合并 customConfig 和 axios.defaults,这则改动之后,我们可以获取到当前 Axios 实例创建时的配置,然后基于这个配置,进行二次创建 Axios。因此,增强了 config 的复用能力。核心
Axios 的核心就是
class Axios
,位于./lib/core/Axios.js
文件中(https://github.com/axios/axios/blob/v1.x/lib/core/Axios.js)。这个文件的结构大致如下constructor
在 constructor 中,我们定义了基本的 instance 配置和 interceptors 拦截链
utils.forEach
两个 utils.forEach 其实做的事情差不多,都是想要为
Axios.prototype[method]
增加对应的方法。关注源码会发现,本质上都是使用了 Axios Class 中定义的request
方法。之所以要区分成两个 utils.forEach,是因为后面的三个方法 (post, put, patch) 在调用的时候支持传入 data,所以需要差异化处理。
request
request 方法可以说是 Axios 最为重要和复杂的部分!实际上做了四件事情
dispatchRequest.call(this, newConfig)
执行请求 + 执行 ResponseInterceptors而其中,最为复杂的部分其实就是 interceptors 相关的了!目前版本中的代码又是判断同步又是判断异步,真的非常复杂(可恶!)。而曾经的 interceptors 逻辑可就简单多了:
曾经的 interceptors 本质上就是构建一个数组 chain,chain 中包含了真正实现请求的方法(
dispatchRequest
),然后往队头不断插入 RequestInterceptors,队尾不断插入 ResponseInterceptors。执行的时候不断调用promise.then()
即可。简单的对比会发现,在现在的源码中,如果我们的
synchronousRequestInterceptors
属性最终是 false,其实完全沿用了之前版本的逻辑!而现在的代码实际上就是多了两件事情
这个改动的动机其实来自于这个 PR axios/axios#2609
在这个 PR 中,发起者提出了这么一种情况 (下面的例子简化了一下)
从直觉上,我们可能会认为发起请求的顺序是
'a' -> 'b' -> 'c'
。但是实际上由于曾经的逻辑会默认将 Axios 发起的请求放到当前事件循环的 microtask 队列中(因为发起请求的逻辑在Promise.resolve(config).then()
中),因此实际发起请求的逻辑是'c' -> 'a' -> 'b'
尽管这不符合我们的直觉,但是尚且可以接受,毕竟可能请求之间只会相差几十毫秒。但是!如果说当前的 macrotask 中有一个非常非常慢的任务呢?
在这种情况下,可能 Axios 发出的请求会比请求 c 慢了几百毫秒甚至数秒,因为要先执行完 macrotask 中的任务。这显然是不符合预期的!因此,才有了这次针对 RequestInterceptor 的改动。
回看 request 方法的源代码,我们会发现,如果没有 RequestInterceptors 或者所有的 RequestInterceptors 都被设置成了 sync。那么在处理 RequestInterceptor 和 dispatchRequest 的时候,我们并不会构造出一个 promise chain,而是直接在 macrotask 中就执行了。
于是上面的例子里面,请求的发起顺序就变成了
'a' -> 'b' -> 'c'
,并且也成功的克服了 Axios 延迟发送请求的问题。拦截器
看完了前面的部分,相信大家对 Axios 的拦截器 Interceptors 并不陌生。拦截器的源码在
./lib/core/InterceptorManager.js
中(https://github.com/axios/axios/blob/v1.x/lib/core/InterceptorManager.js)。dispatchRequest
dispatchRequest
是我们真正发送请求的方法,它的代码在https://github.com/axios/axios/blob/v1.x/lib/core/dispatchRequest.js中,它主要做了三件事情adapter 适配器
那么 adapter 适配器是什么呢?记得我们在开篇说的 Axios 是 isomorphic 的(也即同一套代码可以同时运行在 node.js 环境和浏览器环境中)。在服务端它使用了 node.js 原生的
http
模块,而在浏览器端则使用了XMLHttpRequest
。而 adapter 就是根据当前的环境来选择使用什么方式来真正的发送请求,并且内部进行了一些额外的封装取消请求
Axios 可以主动取消请求也是它的一大特色!这一切都是基于 CancelToken,Axios 提供了两种取消请求的方式
接下来让我们先看一下 Class CancelToken 的结构
static source()
static source() 是一个静态方法,返回类型是一个对象,在上文中我们的 method 1 就是直接调用 source 方法获得一个 {token, cancel} ,然后配置 config.cancelToken = source.token,要取消请求的时候调用 source.cancel()
但是!我们对比一下 source 方法内部的实现,以及 method 2. 我们就会发现其实二者做的是一样的事情!
因此,关键点就是 constructor 做了什么!
constructor(executor)
这里面有几个比较关键的处理
function cancel(message, config, request)
。回忆一下,我们在static source()
方法中的实现,executor 函数会将作为入参的这个cancel
函数赋值给我们声明的一个变量。随后,我们调用那个变量实际上就是调用了这里的 cancel() 函数cancel()
做了什么呢?其实做的最关键的事情就是调用了resolvePromise()
方法。由于在前面的代码中我们执行了resolvePromise = resolve
,因此通过调用这个方法,我们这个 Cancel 实例中的 Promise 状态就会从 pending 变成 fulfilled,随后执行.then()
中注册的逻辑.then()
中注册了什么逻辑呢?这里面实际上只是通过调用token._listeners[i](cancel)
,清空了整个 _listeners 队列_listeners
队列是当有任何 adapter 调用了cancelToken.subscribe
的时候,就会往队列中 push 一个onCanceled
函数。我们再看一下什么时候会触发
subscribe
,以使用 xhr adapter 为例,在xhr.js
中,存在这么一段可以看到,如果请求配置了 cancelToken,就会创建一个
onCanceled
函数,并且调用这个cancelToken.subscribe()
, 将自己的onCanceled
函数输入进去。最后,回到 Class CancelToken 的 Constructor 中,里面有一段覆写了
this.promise.then
,所以我们如果通过某种方式调用了config.cancelToken.promise.then(...)
,那么实际上会走到我们覆写的逻辑里面。那么我们什么时候会这样调用呢!答案是目前不会了!具体可以参考这个 PR Fixed cancelToken leakage; Added AbortController support; #3305。config.cancelToken.promise.then(...)
这个使用方式已经被删除了。之所以保留了这一段代码,是因为不确定有无用户的代码依赖于这个方式来执行,因此仍然选择了保留。但是不推荐后续继续使用啦~
总结
至此,Axios 主要部分的源码已经解读完毕了。如果文章里有任何问题,欢迎阅读者一起探讨!
The text was updated successfully, but these errors were encountered: