搞明白axios 源码,探究配置、拦截器、适配器等核心功能具体的执行过程(二)

简介: 搞明白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, undefined];
  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的最前面。最终形成以下链接:

4edc953e2c684bbe819ffa954c899c08.png

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

var chain = [dispatchRequest, undefined];

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

4edc953e2c684bbe819ffa954c899c08.png

这才是一个最终的 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 !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && 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');

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

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

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

相关文章
|
3月前
axios拦截器
axios拦截器
21 0
|
2月前
|
开发框架 移动开发 JavaScript
SpringCloud微服务实战——搭建企业级开发框架(四十七):【移动开发】整合uni-app搭建移动端快速开发框架-添加Axios并实现登录功能
在uni-app中,使用axios实现网络请求和登录功能涉及以下几个关键步骤: 1. **安装axios和axios-auth-refresh**: 在项目的`package.json`中添加axios和axios-auth-refresh依赖,可以通过HBuilderX的终端窗口运行`yarn add axios axios-auth-refresh`命令来安装。 2. **配置自定义常量**: 创建`project.config.js`文件,配置全局常量,如API基础URL、TenantId、APP_CLIENT_ID和APP_CLIENT_SECRET等。
194 60
|
2月前
|
前端开发 开发工具 数据安全/隐私保护
大事件项目13----axios请求拦截器,统一携带token
大事件项目13----axios请求拦截器,统一携带token
|
2月前
|
前端开发 开发工具 git
大事件项目15----axios响应拦截器,统一判断401做被动退出
大事件项目15----axios响应拦截器,统一判断401做被动退出
|
2月前
|
前端开发 JavaScript API
技术经验分享:axios的全局配置baseUrl
技术经验分享:axios的全局配置baseUrl
31 3
|
1月前
|
JSON JavaScript 前端开发
Vue中的axios深度探索:从基础安装到高级功能应用的全面指南
在Vue项目中,高效的前后端通信是构建丰富用户体验的关键。axios作为前端与后端沟通的桥梁,其重要性不言而喻。本文将带您领略axios的魅力,从基本概念、安装方法,到高级应用技巧,助您快速掌握在Vue中利用axios进行HTTP请求的精髓。我们不仅会探讨axios的基础用法,如GET、POST请求,还将深入探索跨域配置、全局注册以及设置拦截器等高级功能,助您轻松实现优雅的前后端通信。
|
1月前
|
JavaScript 前端开发 数据格式
URL编码【详解】——Javascript对URL进行编码解码的三种方式的区别和使用场景,axios请求拦截器中对get请求的参数全部进行URL编码
URL编码【详解】——Javascript对URL进行编码解码的三种方式的区别和使用场景,axios请求拦截器中对get请求的参数全部进行URL编码
37 0
|
2月前
|
JSON JavaScript API
Vue2和Vue3axios的如何使用,Vue3全局配置axios,axios全局配置
Vue2和Vue3axios的如何使用,Vue3全局配置axios,axios全局配置
|
3月前
|
前端开发 JavaScript 数据格式
vue3中axios添加请求和响应的拦截器
vue3中axios添加请求和响应的拦截器
77 1
|
3月前
|
存储 算法 JavaScript
< 今日小技巧:Axios封装,接口请求增加防抖功能 >
今天这篇文章,主要是讲述对axios封装的请求,由于部分请求可能存在延时的情况。使得接口可能存在会被持续点击(即:接口未响应的时间内,被持续请求),导致重复请求的问题,容易降低前后端服务的性能!故提出给axios封装的配置里面,新增一个防抖函数,用来限制全局请求的防抖。
< 今日小技巧:Axios封装,接口请求增加防抖功能 >