axios拦截器工作方式及原理源码解析

目录
  • axios 拦截器的配置方式
  • use() 方法的定义
  • 拦截器如何执行
    • 拦截器回调方法的添加顺序
    • 同步执行请求拦截器(顺序执行)
    • 异步执行请求拦截器(同时执行)
  • Q&A
    • 拦截器是如何工作的
    • 拦截器的执行顺序
    • 同步&异步

axios 拦截器的配置方式

本文所用 axios 版本号为:1.3.2

axios 中有两种拦截器:

  • axios.interceptors.request.use(onFulfilled, onRejected, options):配置请求拦截器。

    • onFulfilled 方法在发送请求前执行,接收 config 对象,返回一个新的 config 对象,可在此方法内修改 config 对象。
    • onRejected 方法在 onFulfilled 执行错误后执行,接收 onFulfilled 执行后的错误对象。
    • options 配置参数
      • synchronous:控制请求拦截器是否为异步执行,默认为 true,每个拦截器都可以单独设置,只要有一个设置为 false 则为同步执行,否则为异步执行。
      • runWhen:一个方法,接收 config 对象作为参数,在每次调用设定的拦截器方法前调用,返回结果为 true 时执行拦截器方法。
  • axios.interceptors.response.use(onFulfilled, onRejected, options):配置响应拦截器。
    • onFulfilled 方法在返回响应结果前调用,接收响应对象,返回一个新的响应对象,可在此方法内修改响应对象。
    • onRejected 与 options 同 axios.interceptors.request.use
axios.interceptors.request.use(
  function (config) {
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    return Promise.reject(error);
  }
);

可以添加多个拦截器:

axios.interceptors.request.use(
  function (config) {
    throw new Error("999");
    return config;
  },
  function (error) {
    console.log(1, error);
    return Promise.reject(error);
  }
);
axios.interceptors.request.use(
  function (config) {
    throw new Error("888");
    return config;
  },
  function (error) {
    console.log(2, error);
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    console.log(3, error);
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    console.log(4, error);
    return Promise.reject(error);
  }
);
axios.get("https://www.baidwwu.com").catch((error) => {
  console.log(4, error);
});
// 2  Error: 888
// 1  Error: 888
// 3  Error: 888
// 4  Error: 888
// 5  Error: 888

先执行请求拦截器,后执行响应拦截器。

  • 设置多个请求拦截器的情况:后添加的拦截器先执行。
  • 设置多个响应拦截器的情况:按照设置顺序执行。

use() 方法的定义

先来看 use() 方法相关代码:

this.interceptors = {
  request: new InterceptorManager$1(),
  response: new InterceptorManager$1(),
};
var InterceptorManager = (function () {
  function InterceptorManager() {
    this.handlers = [];
  }
  // ...
  // _createClass 方法可以给对象的原型上添加属性
  _createClass(InterceptorManager, [
    {
      key: "use",
      value: function use(fulfilled, rejected, options) {
        this.handlers.push({
          fulfilled: fulfilled,
          rejected: rejected,
          synchronous: options ? options.synchronous : false,
          runWhen: options ? options.runWhen : null,
        });
        return this.handlers.length - 1;
      },
    },
    {
      key: "forEach",
      value: function forEach(fn) {
        utils.forEach(this.handlers, function forEachHandler(h) {
          if (h !== null) {
            fn(h);
          }
        });
      },
    },
    // ...
  ]);
  return InterceptorManager;
})();
var InterceptorManager$1 = InterceptorManager;

可以看到 interceptors.requestinterceptors.response 各自指向 InterceptorManager 的实例。

InterceptorManager 原型上设置了 use() 方法,执行后给待执行队列 this.handlers 中添加一条数据。

这里利用了 this 的特性来区分作用域:谁调用 use() 方法就给谁的 handlers 中添加数据。在调用 use() 方法之后,已经将回调函数按顺序添加到了 handlers 数组中。

forEach() 方法用来遍历当前作用域 handlers 中不为 null 的元素,在执行拦截器时有用到,详情见下文。

拦截器如何执行

拦截器是在调用了 request() 方法前后执行的,先看相关源码:

var Axios = (function () {
  _createClass(Axios, [
    {
      key: "request",
      value: function request(configOrUrl, config) {
        // ...
        // 初始化请求拦截器
        var requestInterceptorChain = [];
        var synchronousRequestInterceptors = true;
        this.interceptors.request.forEach(function unshiftRequestInterceptors(
          interceptor
        ) {
          if (
            typeof interceptor.runWhen === "function" &&
            interceptor.runWhen(config) === false
          ) {
            return;
          }
          synchronousRequestInterceptors =
            synchronousRequestInterceptors && interceptor.synchronous;
          requestInterceptorChain.unshift(
            interceptor.fulfilled,
            interceptor.rejected
          );
        });
        // 初始化响应拦截器
        var responseInterceptorChain = [];
        this.interceptors.response.forEach(function pushResponseInterceptors(
          interceptor
        ) {
          responseInterceptorChain.push(
            interceptor.fulfilled,
            interceptor.rejected
          );
        });
        var promise;
        var i = 0;
        var len;
        // 请求拦截器同步执行模式
        if (!synchronousRequestInterceptors) {
          var chain = [dispatchRequest.bind(this), undefined];
          chain.unshift.apply(chain, requestInterceptorChain);
          chain.push.apply(chain, responseInterceptorChain);
          len = chain.length;
          promise = Promise.resolve(config);
          console.log(11, chain);
          while (i < len) {
            promise = promise.then(chain[i++]).catch(chain[i++]);
          }
          return promise;
        }
        // 请求拦截器异步执行模式
        len = requestInterceptorChain.length;
        var newConfig = config;
        i = 0;
        while (i < len) {
          var onFulfilled = requestInterceptorChain[i++];
          var 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++]
          );
        }
        return promise;
      },
    },
  ]);
})();

上面是相关的全部代码,下面进行分解。

拦截器回调方法的添加顺序

var requestInterceptorChain = []; // 请求拦截器执行链
// 是否同步执行请求拦截器,每个拦截器都可以单独设置,但是只有所有拦截器都设置为true才为true
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
  interceptor
) {
  // 传入 runWhen() 方法时,如果 runWhen() 方法返回值为 false 则忽略这个请求拦截器
  if (
    typeof interceptor.runWhen === "function" &&
    interceptor.runWhen(config) === false
  ) {
    return;
  }
  // 是否同步执行
  synchronousRequestInterceptors =
    synchronousRequestInterceptors && interceptor.synchronous;
  // 用 unshift() 添加数据,后设置的拦截器在前
  requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = []; // 响应拦截器执行链
this.interceptors.response.forEach(function pushResponseInterceptors(
  interceptor
) {
  // 响应拦截器按顺序加在后面
  responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});

interceptors.request.forEach() 的定义在上文提到过,封装为遍历自身的 handlers 数组,与数组的 forEach 类似,只是过滤了值为 null 的元素。

runWhen() 方法接收 config,返回 boolean,控制是否将当前拦截器的回调函数添加到执行链中。

请求拦截器用 unshift() 方法添加,所以后设置的先执行,响应拦截器用 push() 方法添加,所以按照设置顺序执行。

只要有一个拦截器的 synchronous 设置为 false,则 synchronousRequestInterceptors 的值为 false

同步执行请求拦截器(顺序执行)

synchronousRequestInterceptorsfalse 时为同步执行,相关逻辑如下:

var promise;
var i = 0;
var len;
if (!synchronousRequestInterceptors) {
  var 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 = promise.then(chain[i++], chain[i++]); // 用 Promise 包装执行链
  }
  return promise;
}

dispatchRequest() 是发送请求的方法。

同步执行模式下,会将执行链中的所有方法用 Promise 进行封装,前一个方法执行完毕后将其返回值作为参数传递给下一个方法。

这里的 chain 其实就是所有拦截器方法与请求方法合并而成的执行链,等价于: [...requestInterceptorChain, dispatchRequest.bind(this), ...responseInterceptorChain]

从一个例子来看 chain 的成员:

axios.interceptors.request.use(onFulfilled1, onRejected1);
axios.interceptors.request.use(onFulfilled2, onRejected2);
axios.interceptors.response.use(onFulfilled3, onRejected3);
axios.interceptors.response.use(onFulfilled4, onRejected4);
axios.get();
// chain: [onFulfilled2, onRejected2, onFulfilled1, onRejected1, dispatchRequest, onFulfilled3, onRejected3, onFulfilled4, onRejected4]

在构建 Promise 链的时候,一次遍历中取了两个方法传递给 then():

promise = Promise.resolve(config);
while (i < len) {
  promise = promise.then(chain[i++], chain[i++]); // 用 Promise 包装执行链
}
return promise;

异步执行请求拦截器(同时执行)

var promise;
var i = 0;
var len = requestInterceptorChain.length; // 请求执行链的长度
var newConfig = config;
i = 0;
// 执行请求拦截器回调
while (i < len) {
  var onFulfilled = requestInterceptorChain[i++]; // use() 的第一个参数
  var onRejected = requestInterceptorChain[i++]; // use() 的第二个参数
  // 执行成功后继续执行,执行失败后停止执行。
  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++], // use() 的第一个参数
    responseInterceptorChain[i++] // use() 的第二个参数
  );
}
return promise;

dispatchRequest() 是发送请求的方法。

while 循环遍历所有的请求拦截器并调用,将执行语句包裹在 try-catch 语句中,只要有一个请求拦截器异常就停止循环。

可以看到在异步模式下,请求拦截器为异步执行,但是不影响发送请求,而响应拦截器还是在请求响应后同步执行。

Q&A

拦截器是如何工作的

调用 .request.use().response.use() 方法时,将传入的拦截器回调方法分别存入 请求拦截器回调数组响应拦截器回调数组

在调用 .get() 等方法时,将 请求拦截器回调数组响应拦截器回调数组 与发送请求的方法拼接成一个完整的执行链,按照同步或异步的模式调用执行链中的方法。

响应拦截器总是在返回响应结果后按顺序执行。

请求拦截器根据 synchronous 配置不同,行为有所不同:

  • 异步执行请求拦截器模式。
  • 没有设置 synchronous 时默认为异步执行请求拦截器模式,即遍历执行所有的请求拦截器一参回调,执行报错后停止遍历,并执行该拦截器的二参回调。
  • 同步执行请求拦截器模式。
  • 设置 synchronousfalse 时为同步执行请求拦截器模式,将执行链包装成一个 Promise 链顺序执行。

拦截器的执行顺序

先执行请求拦截器,后执行响应拦截器。

  • 设置多个请求拦截器的情况:后添加的拦截器先执行。
  • 设置多个响应拦截器的情况:按照设置顺序执行。

同步&异步

计算机中的同步,指的是现实中的一步一步(同一时间只能干一件事),异步指的是同时进行(同一时间能干多件事)。

参考 npm axios

以上就是axios拦截器工作方式及原理源码解析的详细内容,更多关于axios拦截器工作原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue3 使用axios拦截器打印前端日志

    目录 一.前言 二.使用axios拦截器打印前端日志 一.前言 很多时候我们需要对前端进行调试,也就是前后端接口之间交互的调试,常用的方式肯定是打日志了,如console.log ('日志内容'). 就单个方法其实用这种方法是可以的,多个接口和方法,这样的调试方法就差了一些,再有就是方法有执行顺序,有时候反倒影响调试了. 二.使用axios拦截器打印前端日志 这是一种比较值得推荐的方式,也就是写一次,就不用总写console.log了. 突然想到,做测试时候,常看到的一句话: 一切都是为了测试

  • Typescript 封装 Axios拦截器方法实例

    目录 引言 创建 class axios.create([config]) 封装 request(config)通用方法 封装-拦截器(单个实例独享) 扩展 Http 自定义拦截器 封装-拦截器(所有实例共享) 封装-拦截器(单个请求独享) 装修 Http class 返回经过 request 返回数据结构(DTO) 拦截器执行顺序 操作场景控制 引言 对 axios 二次封装,更加的可配置化.扩展性更加强大灵活 通过 class 类实现,class 具备更强封装性(封装.继承.多态),通过实例

  • axios 拦截器管理类链式调用手写实现及原理剖析

    目录 axios库的拦截器使用 整体设计 拦截器管理类实现 接口定义 代码实现 链式调用实现 axios库的拦截器使用 我们知道axios库的拦截器的使用方式如下: // 添加一个请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前可以做一些事情 return config; }, function (error) { // 处理请求错误 return Promise.reject(error); }); // 添

  • vue中Axios添加拦截器刷新token的实现方法

    目录 1. Axios基本用法: 2. Axios基本封装用法: 3. 添加拦截器的用法 4. 注意事项: Axios是一款网络前端请求框架,基本用法如下: 1. Axios基本用法: const response = await Axios.create({ baseURL: "https://test.api.com", headers: { 'Content-Type': 'application/json', }, }).post<RequestResponse>(

  • vue cli+axios踩坑记录+拦截器使用方式,代理跨域proxy

    目录 1.首先axios不支持vue.use()方式声明使用 2.小小的提一下vue cli脚手架 3.axios发送get post请求问题 4.axios拦截器的使用 1.首先axios不支持vue.use()方式声明使用 看了所有近乎相同的axios文档都没有提到这一点 建议方式 在main.js中如下声明使用 import axios from 'axios'; Vue.prototype.$axios=axios; 那么在其他vue组件中就可以this.$axios调用使用 2.小小的

  • axios拦截器工作方式及原理源码解析

    目录 axios 拦截器的配置方式 use() 方法的定义 拦截器如何执行 拦截器回调方法的添加顺序 同步执行请求拦截器(顺序执行) 异步执行请求拦截器(同时执行) Q&A 拦截器是如何工作的 拦截器的执行顺序 同步&异步 axios 拦截器的配置方式 本文所用 axios 版本号为:1.3.2. axios 中有两种拦截器: axios.interceptors.request.use(onFulfilled, onRejected, options):配置请求拦截器. onFulfil

  • Spring的Model 和 Map的原理源码解析

    Model 和 Map 为什么在Model和Map中放值传入后会出现在request的上面. 9.1.源码解析 准备测试代码 @GetMapping("/goto") public String go(HttpServletRequest request, Map<String,Object> map, Model model){ request.setAttribute("msg","传过来...."); map.put("

  • React Hydrate原理源码解析

    目录 引言 Demo ReactDOM.render ReactDOM.hydrate hydrate 过程 事件绑定 hydrate 源码剖析 beginWork HostRoot Fiber HostComponent HostText Fiber tryToClaimNextHydratableInstance completeUnitOfWork popHydrationState prepareToHydrateHostInstance prepareToHydrateHostText

  • MyBatis框架底层的执行原理源码解析

    目录 1.前言 2.案例项目源码 3.MyBatis源码解析底层执行原理 3.1 读取mybatis配置文件创建出SqlSeesionFactory对象 3.2 通过SqlSeesionFactory对象进而创建出SqlSession对象 3.3 通过SqlSession的getMapper获取到接口代理对象 3.4 通过mapper接口的代理对象执行CRUD 1.前言 MyBatis框架大家肯定都用过的,废话我就不再多说了,这篇文章就给大家分享一下有关MyBatis框架底层的执行原理吧(Deb

  • go slice 扩容实现原理源码解析

    目录 正文 扩容的示例 实际扩容倍数 growslice 实现 growslice 实现步骤 growslice 源码剖析 总结 正文 基于 Go 1.19. go 的切片我们都知道可以自动地进行扩容,具体来说就是在切片的容量容纳不下新的元素的时候, 底层会帮我们为切片的底层数组分配更大的内存空间,然后把旧的切片的底层数组指针指向新的内存中: 目前网上一些关于扩容倍数的文章都是基于相对旧版本的 Go 的,新版本中,现在切片扩容的时候并不是那种准确的小于多少容量的时候就 2 倍扩容, 大于多少容量

  • async-validator实现原理源码解析

    目录 async-validator 介绍 async-validator 基本使用 async-validator 源码分析 async-validator 源码-构造函数 async-validator 源码-validate方法 async-validator 源码-register方法 总结 最后 async-validator 介绍 async-validator是异步的验证数据是否合法有效的工具, 内置了不同数据类型的常见验证规则. 在需要对数据进行验证的场景中,都可以考虑使用asy

  • vue cli+axios踩坑记录+拦截器使用方式,代理跨域proxy

    目录 1.首先axios不支持vue.use()方式声明使用 2.小小的提一下vue cli脚手架 3.axios发送get post请求问题 4.axios拦截器的使用 1.首先axios不支持vue.use()方式声明使用 看了所有近乎相同的axios文档都没有提到这一点 建议方式 在main.js中如下声明使用 import axios from 'axios'; Vue.prototype.$axios=axios; 那么在其他vue组件中就可以this.$axios调用使用 2.小小的

  • vue axios拦截器常用之重复请求取消

    引言 上一篇介绍了axios的简单封装,知道了axios拦截器的应用场景和方法,今天来看一下对于响应时间过长且请求次数过高的情况拦截器如何处理. 取消请求的方法 Axios使用内部提供的CancelToken来取消请求 官网示例1:用CancelToken.source工厂方法创建 cancel token,像这样 const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/use

随机推荐