上一篇,我们扩展了Axios,构建了一个Axios类,然后通过这个Axios工厂类,创建真正的axios实例。那么今天,我们来实现下Axios的拦截器也就是interceptors。我们来简单看下Axios的interceptors的API:

  首先我们来看,axios上有一个interceptors属性,该属性上还有两个属性,分别对应request和response,并且都有一个一样的use方法,该方法目前有两个参数,分别对应着Promise中的resolve和reject。

  另外,你还可以通过对应拦截器的eject方法,移除某个拦截器。

  最后,我们还可以通过配置第三个参数,确定执行拦截器的条件、是否异步等。最后的最后,我们还需要知道拦截器的执行顺序,我们先来看一段代码:

  1. axios.interceptors.request.use((config) => {
  2. config.headers.test += "1";
  3. return config;
  4. });
  5. axios.interceptors.request.use((config) => {
  6. config.headers.test += "2";
  7. return config;
  8. });
  9. axios.interceptors.request.use((config) => {
  10. config.headers.test += "3";
  11. return config;
  12. });
  13.  
  14. axios.interceptors.response.use((res) => {
  15. res.data += "1";
  16. return res;
  17. });
  18. let c5 = axios.interceptors.response.use((res) => {
  19. res.data += "2";
  20. return res;
  21. });
  22. axios.interceptors.response.use((res) => {
  23. res.data += "3";
  24. return res;
  25. });
  26.  
  27. axios.interceptors.response.eject(c5);
  28.  
  29. axios({
  30. url: "/c5/get",
  31. method: "get",
  32. headers: {
  33. test: "",
  34. },
  35. }).then((res) => {
  36. console.log(res.data);
  37. });

  这是我们最终demo里的代码,它的结果是什么样子呢?我得在这里就给出大家答案,不然有个核心的点大家可能就不理解了。其中request的header中的tes的值是321,打印的response的结果是13。OK,依照此我们可以得出结论,就是越靠近请求的拦截器越先执行,什么意思呢?就是我们文档流中写在后面的请求拦截器最先执行,写在前面的响应拦截器最先执行。它是一种以中心向外散射的一种模型。

  那么我们接下来看怎么来实现这个拦截器吧:

  1. "use strict";
  2.  
  3. import utils from "./../utils";
  4.  
  5. function InterceptorManager() {
  6. this.handlers = [];
  7. }
  8.  
  9. /**
  10. * Add a new interceptor to the stack
  11. *
  12. * @param {Function} fulfilled The function to handle `then` for a `Promise`
  13. * @param {Function} rejected The function to handle `reject` for a `Promise`
  14. *
  15. * @return {Number} An ID used to remove interceptor later
  16. */
  17. InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  18. this.handlers.push({
  19. fulfilled: fulfilled,
  20. rejected: rejected,
  21. synchronous: options ? options.synchronous : false,
  22. runWhen: options ? options.runWhen : null,
  23. });
  24. return this.handlers.length - 1;
  25. };
  26.  
  27. /**
  28. * Remove an interceptor from the stack
  29. *
  30. * @param {Number} id The ID that was returned by `use`
  31. */
  32. InterceptorManager.prototype.eject = function eject(id) {
  33. if (this.handlers[id]) {
  34. this.handlers[id] = null;
  35. }
  36. };
  37.  
  38. /**
  39. * Iterate over all the registered interceptors
  40. *
  41. * This method is particularly useful for skipping over any
  42. * interceptors that may have become `null` calling `eject`.
  43. *
  44. * @param {Function} fn The function to call for each interceptor
  45. */
  46. InterceptorManager.prototype.forEach = function forEach(fn) {
  47. utils.forEach(this.handlers, function forEachHandler(h) {
  48. if (h !== null) {
  49. fn(h);
  50. }
  51. });
  52. };
  53.  
  54. export default InterceptorManager;

  首先,我们在core文件夹下创建一个InterceptorManager.js,代码如上,在文件内我们构建一个InterceptorManager类,这个类上只有一个数组作为存储具体拦截器的容器。

  然后呢,我们在它的原型上挂载一个use方法,这个前面说过了,就是要把具体的拦截器放置到容器内,以待最后的使用,其中放置的是一个包含了resolve和reject函数以及两个参数的对象,这个方法返回了一个对应拦截器在容器内的下标作为id。

  再然后呢,就是一个eject方法,使用use方法中返回的下标,直接设置为null即可,提问!为啥这里不直接移除(splice啥的)容器内的拦截器,而是把对应位置的拦截器设置为null呢?

  最后,我们提供一个forEach方法,循环执行容器内的拦截器即可。那么到现在为止,整个拦截器管理类就实现了。下面我们看看如何使用。

  1. Axios.prototype.request = function (configOrUrl, config) {
  2. if (typeof configOrUrl === "string") {
  3. if (!config) {
  4. config = {};
  5. }
  6. config.url = configOrUrl;
  7. } else {
  8. config = configOrUrl;
  9. }
  10. // 请求拦截器调用链
  11. var requestInterceptorChain = [];
  12. // 是否同步
  13. var synchronousRequestInterceptors = true;
  14. // 通过拦截器的forEach方法,通过回调函数的方式,把所有的请求拦截放到requestInterceptorChain数组里
  15. this.interceptors.request.forEach(function unshiftRequestInterceptors(
  16. interceptor
  17. ) {
  18. if (
  19. // 判断下如果runWhen是false就return掉了
  20. typeof interceptor.runWhen === "function" &&
  21. interceptor.runWhen(config) === false
  22. ) {
  23. return;
  24. }
  25. // 判断是否是同步执行
  26. synchronousRequestInterceptors =
  27. synchronousRequestInterceptors && interceptor.synchronous;
  28. // 把两个回调函数放到数组的头部
  29. // 注意这里不是unshift一个数组,而是独立的,就是这样[interceptor.fulfilled,interceptor.rejected]
  30. // [3,2,1]
  31. requestInterceptorChain.unshift(
  32. interceptor.fulfilled,
  33. interceptor.rejected
  34. );
  35. });
  36. // 响应拦截器调用链
  37. var responseInterceptorChain = [];
  38. // response这个比较简单,直接push进数组就完事了
  39. this.interceptors.response.forEach(function pushResponseInterceptors(
  40. interceptor
  41. ) {
  42. responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  43. });
  44. // 定一个promise变量,后面用
  45. var promise;
  46. // 如果不是同步的
  47. if (!synchronousRequestInterceptors) {
  48. var chain = [dispatchRequest, undefined];
  49. // 这块呢,就把整个requestInterceptorChain放到chain的前面
  50. Array.prototype.unshift.apply(chain, requestInterceptorChain);
  51. // 这个就是把responseInterceptorChain放到[requestInterceptorChain,chain]后面
  52. chain = chain.concat(responseInterceptorChain);
  53. // 额外要说的是到了这里,这个chain数组是什么样的呢
  54. // 我们打印下,以我们之前的例子代码为例:
  55. // 它实际上是这样的[fn,undefined,fn,undefined,fn,undefined,fn,undefined,fn,undefined,fn,undefined]
  56. // 具体点,[requestInterceptorChain,chain,responseInterceptorChain]
  57. // 再具体点:[requestResolve3,undefined,requestResolve2,undefined,requestResolve1,undefined,dispatchRequest, undefined,responseResolve1,undefined,responseResolve3,undefined]
  58. console.log(chain, "chian");
  59. // 这块可能就优点疑惑了,首先promise变量变成了一个已经resolved的Promise,resolve出去的就是config配置
  60. promise = Promise.resolve(config);
  61. while (chain.length) {
  62. // 所以这里的then里面就是这样(resolve,reject)
  63. // 注意then方法的第二个参数就是reject的。
  64. // 换句话说,这里就形成了一个一个的链式调用,源头是一个已经resolved的promise。
  65. promise = promise.then(chain.shift(), chain.shift());
  66. }
  67. // 返回咯
  68. return promise;
  69. }
  70. // 那如果是同步的话,走下面的代码
  71. // 很简单,就是同步执行罢了,我就不说了哦。
  72. var newConfig = config;
  73. while (requestInterceptorChain.length) {
  74. var onFulfilled = requestInterceptorChain.shift();
  75. var onRejected = requestInterceptorChain.shift();
  76. try {
  77. // 新的config就是onFulfilled同步函数执行的结果,一步一步往下传
  78. newConfig = onFulfilled(newConfig);
  79. } catch (error) {
  80. onRejected(error);
  81. break;
  82. }
  83. }
  84. // 执行dispatchRequest返回个promise,dispatchRequest本身就会返回promise,对吧?
  85. try {
  86. promise = dispatchRequest(newConfig);
  87. } catch (error) {
  88. return Promise.reject(error);
  89. }
  90. // 循环执行responseInterceptorChain链。
  91. while (responseInterceptorChain.length) {
  92. promise = promise.then(
  93. responseInterceptorChain.shift(),
  94. responseInterceptorChain.shift()
  95. );
  96. }
  97. // 返回,结束
  98. return promise;
  99. };

  上面是完整的request方法的注释,还算清晰,大家也可以去gitHub上查看。那,简单回顾下,整个执行的核心其实分为了同步和异步,但是其实整体的代码都不复杂,就是调用的时候会稍微绕一点。requestInterceptorChain通过unshift后添加的就变成的数组的头部,先添加的就变成了数组的尾部。通过while循环,每次都shift出去对应的回调函数并执行返回promise,这是异步的做法,同步的做法就比较简单,同步执行requestInterceptorChain,然后在调用request的时候,返回promise,包括后面的responseInterceptorChain也是promise,因为最后要抛出promise供axios实例使用。

  好了,今天的逻辑稍微复杂些,但是本身并不是很难,例子已经在gitHub上了,大家可以亲自去体验下。

一比一还原axios源码(五)—— 拦截器的更多相关文章

  1. 一比一还原axios源码(零)—— 概要

    从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不 ...

  2. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  3. 一比一还原axios源码(四)—— Axios类

    axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...

  4. 一比一还原axios源码(一)—— 发起第一个请求

    上一篇文章,我们简单介绍了XMLHttpRequest及其他可以发起AJAX请求的API,那部分大家有兴趣可以自己去扩展学习.另外,简单介绍了怎么去读以及我会怎么写这个系列的文章,那么下面就开始真正的 ...

  5. 一比一还原axios源码(三)—— 错误处理

    前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...

  6. 一比一还原axios源码(六)—— 配置化

    上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的.首先,按照惯例我们来看看axios的文档是怎么说的: 首先我们可以可以通过axios上的defaults属性来配置api. 我们可 ...

  7. Struts2 源码分析——拦截器的机制

    本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...

  8. springMVC源码分析--拦截器HandlerExecutionChain(三)

    上一篇博客springMVC源码分析--HandlerInterceptor拦截器调用过程(二)中我们介绍了HandlerInterceptor的执行调用地方,最终HandlerInterceptor ...

  9. Struts2 源码分析-----拦截器源码解析 --- ParametersInterceptor

    ParametersInterceptor拦截器其主要功能是把ActionContext中的请求参数设置到ValueStack中,如果栈顶是当前Action则把请求参数设置到了Action中,如果栈顶 ...

随机推荐

  1. centos安装Libzip

    2018年06月29日 11:12:15 oxiaobaio 阅读数 4827   wget https://nih.at/libzip/libzip-1.2.0.tar.gztar -zxvf li ...

  2. Yosemite下安装jdk、mysql、maven、idea

    Mac OS X Yosemite已经在2014年10月17日正式发布了. 作为一个java开发者,尝鲜第一时间安装了最新版本. 和之前的OS X系统还是有很多不同的.下面主要在java开发环境方面做 ...

  3. 有手就行——Jenkins介绍 及 Gitlab代码托管服务器安装

    Jenkins 介绍 及 Gitlab代码托管服务器安装 Gitlab安装 源码上传到Gitlab仓库 Jenkins介绍 Jenkins 是一款流行的开源持续集成(Continuous Integr ...

  4. Nvicat for mysql连接腾讯云数据库TDSQL-C(原CynosDB)

    连接步骤 1.打开腾讯云TDSQL-C(原CynosDB)集群详情中的中的集群连接外网地址,如下图 2.云数据库的账号密码在这里找,第一次不知道密码可以重置密码 3.以root账户为例,打开navic ...

  5. 【HDU6687】Rikka with Stable Marriage(Trie树 贪心)

    题目链接 大意 给定\(A,B\)两个数组,让他们进行匹配. 我们称\(A_i\)与\(B_j\)的匹配是稳定的,当且仅当目前所剩元素不存在\(A_x\)或\(B_y\)使得 \(A_i\oplus ...

  6. MySQL之视图篇

    MySQL之视图篇 文章目录 MySQL之视图篇 1. 概述 1.1 为什么使用视图? 1.2 视图的理解 2. 创建视图 2.1 创建单表视图 2.2 针对于多表 2.3 基于视图创建视图 3. 查 ...

  7. zeppelin安装及配置

    1.下载安装包,zepplin下载地址:http://zeppelin.apache.org/download.html #创建解压目录 mkdir -p /opt/software #解压 tar ...

  8. AppiumForWin安装

    尝试安装Windows版本的Appium 参考:http://www.cnblogs.com/fnng/p/4540731.html 第一步:安装node https://nodejs.org/en/ ...

  9. mysql对属性的增删改

    修改表 alter table 创建表db 查看表 desc与describe desc table 查看建表语句show create table t1; 修改表名 alter table t1 r ...

  10. Sunlogin RCE漏洞分析和使用

    介绍   前两天网上曝出了关于向日葵远控工具(Sunlogin)Windows个人版的RCE漏洞POC.因为利用简单并且网上出现了公开的自动化扫描脚本,所以测试的人很多,也出现了一些真实攻击.漏洞的问 ...