搞明白axios源码,探究配置、拦截器、适配器的执行过程

前言

上一节我们简单地介绍了一下 axios 整体加载流程和使用过程。可以清楚地了解到当时 import axios from axios 之后 这背后到底做了什么。并且我们也简单介绍了一个 axios 到底是一个什么类型的数据。以及为什么可以即可以当成方法调用还可以通过对象的调用方式调用某些属性方法

如果没有了解的同学可以先去看一下上一篇文章的介绍,再继续往下看。

这篇我们主要讲解一下 axios 中的 配置、拦截器和执行链等一些核心的功能到底是怎么运行的。

配置过程

要了解这个之前,我们先来看一下 axios 在使用的时候一种方式:

axios.create({ ...配置项 })

不知道大家有没有使用过这种方式,这种方式可以让我们传递一些配置到 axios 的内部,具体实现如下:

axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); };

没错,最终又调用了 createInstance 函数,再来看一下函数体吧:

function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); return instance; }

有一个函数需要关注一下就是 mergeConfig, 这个函数会把 axios 自带的配置和我们传入的配置进行合并,我们传入的配置会覆盖 axios 自带的配置,也就是说我们传入的配置优先级会更高。

由于这个 mergeConfig 函数体积太大,我们就不细说了,大家有兴趣可以看一下源码。

这里要继续说一下,我们在发送某个具体的请求的时候也可以进行配置,这样就有三个配置。

优先级依次是:某个具体请求配置 > 创建实例对象配置 > axios 默认配置

请求过程

上节说过,axios可以像对象那样调用属性方法,如 get、post等,其实最终都会调用 request 方法,代码如下:

utils.forEach([delete, get, head, options], function forEachMethodNoData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: (config || {}).data })); }; }); utils.forEach([post, put, patch], function forEachMethodWithData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, data, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: data })); }; });

可以看出最终都会调用到 this.request 方法。那我们来重点看一下 request方法具体做了什么。

先看一下函数体吧,代码也不是很多:

Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios(example/url[, config]) a la fetch API if (typeof config === string) { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } config = mergeConfig(this.defaults, config); // Set config.method if (config.method) { config.method = config.method.toLowerCase(); } else if (this.defaults.method) { config.method = this.defaults.method.toLowerCase(); } else { config.method = get; } // Hook up interceptors middleware var chain = [dispatchRequest, ]; var promise = Promise.resolve(config); this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; };

主要有三点:

1、生成配置项

2、生成拦截器、执行链

3、返回执行链的结果

下面我们重点介绍一下 2 是如何生成拦截器和执行链的

每个axios实例都会有一个 interceptors 属性,如下:

function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; }

interceptors里面存放着 request 拦截器和response拦截器。InterceptorManager 中有一个 handlers 属性,是一个数组存放着具体的拦截器,再来看一个比较熟悉的方法:

InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1; };

相信大家肯定用过这个 use 方法,这个方法接收两个函数类型的参数,再封装成一个对象放到 handlers中。再回到 request 函数体中,看一下

this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); });

通过遍历把 handlers 的拦截器都放到一个 chain 中,尤其要注意:this.interceptors.request 这个操作,是把最后的拦截器放到 chain的最前面。最终形成以下链接:

当然这还不是最终的 chain,因为前面

var chain = [dispatchRequest, ];

有这样行代码,所以最终的 chain 应该是下面的:

这才是一个最终的 chain 。也就是说我们执行的每个请求都是执行了一个链,最终返回了一个 promise对象,是不是感觉也没有那么神秘,看一下执行代码,很简单

var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise;

以上便是 axios 发送某个请求的全过程,那么接下来我们继续看一下到底是怎么发送的请求。

具体请求

从上面我们可以看到axios发送的请求就是一个链的执行过程,除去 request 和 response的拦截器不说,我们重点说一下:dispatchRequest 这个方法的执行过程,因为具体的请求就是在这个方法中执行的。先来看一下源码:

module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Ensure headers exist config.headers = config.headers || {}; // Transform request data config.data = transformData( config.data, config.headers, config.transformRequest ); // Flatten headers config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers ); utils.forEach( [delete, get, head, post, put, patch, common], function cleanHeaderConfig(method) { delete config.headers[method]; } ); var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // Transform response data response.data = transformData( response.data, response.headers, config.transformResponse ); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // Transform response data if (reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); }); };

方法本身并不难理解,处理一下请求头然后再通过转换器转一下请求数据,最后通过一个适配器执行请求。下面我们再看一下适配器是什么,看一下下面的代码

var adapter = config.adapter || defaults.adapter;

适配器是通过配置获取的,平时的开发中我们几乎不需要自己定义适配器,一般都是用系统默认的,所以我们看一下默认的适配器是怎么样的。下面是默认配置的代码:

adapter: getDefaultAdapter(),

继续看:

function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== ) { // For browsers use XHR adapter adapter = require(./adapters/xhr); } else if (typeof process !== && Object.prototype.toString.call(process) === [object process]) { // For node use HTTP adapter adapter = require(./adapters/http); } return adapter; }

看到这里应该大体的有点明白了吧,其实就是我们平时用的 XMLHttpRequest 对象,那为什么还要做一个适配器呢,主要是因为 axios 不仅仅是一款可以用在 浏览器的库,在 node 开发中也可以使用,但node中没有 XMLHttpRequest对象,就得通过其它的方式实现。本文不涉及 node,所以我们主要看以下代码

adapter = require(./adapters/xhr);

因为代码比较多,所以这里我用图片的形式展示一下:

到这里,我们才真正看到了熟悉的 XMLHttpRequest对象。其实axios底层也就是用的 XMLHttpRequest对象而已,没有什么神秘的。只不过人家封装的很好用起来方便。

其实到这里我们就已经把 axios的整体源码分析了一次,当然还有很多细节没有说到,比如:错误处理,状态码处理等,大家有兴趣的可以自己去细读源码。只有自己阅读一次才能更好的理解 axios的优雅之处。