源码解析axios拦截器

发布时间 2023-11-17 16:12:38作者: 柯基与佩奇

从源码解析axios拦截器是如何工作的

axios 拦截器的配置方式

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源码解析拦截器的执行顺序

题目

// 发送请求的函数
function dispatchRequest(config){
  console.log('发送请求');
  return new Promise((resolve, reject) => {
    resolve({
      status: 200,
      statusText: 'OK'
    });
  });
}
......
// 设置两个请求拦截器
axios.interceptors.request.use(function one(config) {
  console.log('请求拦截器 成功 - 1号');
  return config;
}, function one(error) {
  console.log('请求拦截器 失败 - 1号');
  return Promise.reject(error);
});

axios.interceptors.request.use(function two(config) {
  console.log('请求拦截器 成功 - 2号');
  return config;
}, function two(error) {
  console.log('请求拦截器 失败 - 2号');
  return Promise.reject(error);
});

// 设置两个响应拦截器
axios.interceptors.response.use(function (response) {
  console.log('响应拦截器 成功 1号');
  return response;
}, function (error) {
  console.log('响应拦截器 失败 1号')
  return Promise.reject(error);
});

axios.interceptors.response.use(function (response) {
  console.log('响应拦截器 成功 2号')
  return response;
}, function (error) {
  console.log('响应拦截器 失败 2号')
  return Promise.reject(error);
});


// 发送请求
axios({
  method: 'GET',
  url: 'http://localhost:3000/posts'
}).then(response => {
  console.log(response);
});

// 输出结果---->
// 请求拦截器 成功 - 2号
// 请求拦截器 成功 - 1号
// 发送请求
// 响应拦截器 成功 1号
// 响应拦截器 成功 2号

上图就是整个流程的示意图

啊?到底咋回事啊?还是翻翻源码看看 axios 拦截器的机制吧~

如果只对题目感兴趣的同学可以直接去第四部分。开始吧~

1.源码项目结构

├── /dist/ 			# 输出目录
├── /lib/ 			# 源码目录
│ ├── /adapters/ 		# 定义请求的适配器
│ │ ├── http.js 	        # 实现 http 适配器(包装 http 包)
│ │ └── xhr.js 	                # 实现 xhr 适配器(包装 xhr 对象)
│ ├── /cancel/ 		        # 定义取消功能
│ ├── /core/ 			# 核心功能
│ │ ├── Axios.js 		        # axios 的核心功能
│ │ ├── dispatchRequest.js 	        # 根据环境发送相应请求的函数
│ │ ├── InterceptorManager.js 	        # 拦截器管理
│ │ └── settle.js 		        # 根据 http 响应状态,改变 Promise 的状态
│ ├── /helpers/ 		# 辅助工具函数
│ ├── axios.js 			# 对外暴露接口
│ ├── defaults.js 		# axios 的默认配置
│ └── utils.js 			# 公用工具
├── package.json 		# 项目信息
├── index.d.ts 			# 配置 Typescript 的声明文件
└── index.js 			# 入口文件

2.axios 创建实例过程

// \node_modules\axios\lib\axios.js

......
// 导入默认配置
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 * 创建一个 Axios 的实例对象
 */
function createInstance(defaultConfig) {
  // 创建一个实例对象 context,此时它身上有 get、post、put等方法可供我们调用
  var context = new Axios(defaultConfig);	// 此时,context 不能当函数使用
  // 将 request 方法的 this 指向 context 并返回新函数,此时,instance 可以用作函数使用,
  // 且其与 Axios.prototype.request 功能一致,且返回的是一个 promise 对象
  var instance = bind(Axios.prototype.request, context);
  // 将 Axios.prototype 和实例对象 context 的方法都添加到 instance 函数身上
  // 也就是说,我们此后可以通过 instance.get instance.post ... 的方式发送请求
  utils.extend(instance, Axios.prototype, context);
  utils.extend(instance, context);

  return instance;
}

// 通过配置创建 axios 函数,
// 下面的defaults就是上方顶部通过require('./defaults') 引入的默认配置,
// 就是此前我们提到的,可以通过 axios.defaults.xxx 的方式设置的默认配置
var axios = createInstance(defaults);

// 工厂函数  用来返回创建实例对象的函数
axios.create = function create(instanceConfig) {
    return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose all/spread
axios.all = function all(promises) {
    return Promise.all(promises);
};

得到的信息:

  1. axios实例对象是由createInstance函数创建的
  2. createInstance 函数内部主要功能是把 Axios 构造函数的属性、原型上的request方法和原型都添加到axios实例对象上
  3. 作用是将 Axios 实例封装成一个函数,使得调用时更加简单直观,不再需要实例化Axios对象。

2.1 content 变量(Axios 实例对象)

    // \node_modules\axios\lib\core\Axios.js

    // Axios 构造函数文件

    // 引入工具
    var InterceptorManager = require('./InterceptorManager');
    // 引入发送请求的函数
    var dispatchRequest = require('./dispatchRequest');
    ......

    /**
     * 创建 Axios 构造函数
     */
    function Axios(instanceConfig) {
      // 实例对象上的 defaults 属性为配置对象
      this.defaults = instanceConfig;
      // 实例对象上有 interceptors 属性,用来设置请求和响应拦截器
      this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
      };
    }

    /**
     * 发送请求的方法.  原型上配置, 则实例对象就可以调用 request 方法发送请求
     */
    Axios.prototype.request = function request(config) {
      ......
      var promise = Promise.resolve(config);
      ......
      return promise;
    }

    // 在通过 Axios() 构造函数创建出来的实例对象上添加 get、post、put等方法
    utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
        /*eslint func-names:0*/
        Axios.prototype[method] = function (url, config) {
            return this.request(utils.merge(config || {}, {
                method: method,
                url: url
            }));
        };
    });

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

先回忆一下 axios.request()、axios.post()方法的使用:

axios
  .request({
    method: "GET",
    url: "http://localhost:3000/comments",
  })
  .then((response) => {
    console.log(response);
  });
axios
  .post("http://localhost:3000/comments", {
    body: "喜大普奔",
    postId: 2,
  })
  .then((response) => {
    console.log(response);
  });

得到的信息:

  1. context  变量身上有  defaults  配置对象和  interceptors  拦截器这两个属性。
  2. 通过 utils.forEach() 函数使得 content 变量上绑定了各种发送请求的方法(在 2 中有体现),而各种请求方法实质上都是调用了 Axios.prototype.request 方法。
  3. 创建一个Axios实例对象content的作用就是方便将  defaults  配置对象和  interceptors  拦截器这两个属性添加到instance变量上,最终目的是为了大标题 2 中的第三个信息。我们继续往下看~

2.2 instance 变量

// 将 request 方法的 this 指向 context 并返回新函数,此时,instance 可以用作函数使用,
// 且其与 Axios.prototype.request 功能一致,且返回的是一个 promise 对象
var instance = bind(Axios.prototype.request, context);
// 将 Axios.prototype 和实例对象 context 的方法都添加到 instance 函数身上
// 也就是说,我们此后可以通过 instance.get instance.post ... 的方式发送请求
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);

9174b84037ae4932a2205bd13fd09bb0~tplv-k3u1fbpfcp-watermark.png

这样我们就把Axios实例对象函数化了。

2.3 axios.create() 工厂函数

// 工厂函数  用来返回创建实例对象的函数
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

在实际开发中,使用axios.create()函数创建 Axios 实例是很常见的,并非单例模式。

axios 和 Axios 的关系

其实就是区别  axios  和上文提到的  context 。

  • 从语法上来说: axios  不是  Axios  的实例(上文提到的  context  才是)
  • 从功能上来说: axios  是  Axios  的实例(主要是上方提到的  bind()  和  extend()  函数的功劳)
  • axios  作为对象,有  Axios  原型对象上的所有方法,有  Axios  对象上所有属性
  • axios  是  Axios.prototype.request  函数  bind()  返回的函数

instance 与 axios 的区别

  1. 相同: (1) 都是一个能发任意请求的函数: request(config) (2) 都有发特定请求的各种方法: get()/post()/put()/delete() (3) 都有默认配置和拦截器的属性: defaults/interceptors
  2. 不同: (1) 默认配置很可能不一样 (2) instance  没有  axios  后面添加的一些方法: create()/CancelToken()/all()

我觉得可以这样认为:axios  可以看做是  instance  的超集。主要是因为  axios  被创建出来之后,源码中还在其身上添加了诸如  create()all() 、CancelToken()spread()这些方法和  Axios  属性。

3. axios 发送请求过程

先回忆一下 axios 最基本的用法:

axios({
  //请求类型
  method: "PUT",
  //URL
  url: "http://localhost:3000/posts/3",
  //设置请求体
  data: {
    title: "今天天气不错, 还挺风和日丽的",
    author: "李四",
  },
}).then((response) => {
  console.log(response);
});

源码部分:

// \node_modules\axios\lib\core\Axios.js

// 这个函数是用于发送请求的中间函数,真正发送AJAX请求的操作是被封装在
// \node_modules\axios\lib\adapters\xhr.js 这个文件中的
Axios.prototype.request = function request(config) {
  // 下面这个 if 判断主要是允许我们通过 axios('http://www.baidu.com', {header:{}})
  // 的方式来发送请求,若传入的config是字符串格式,则将第一个参数当做url,第二个参数当做配置对象
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }
  //将默认配置与用户调用时传入的配置进行合并
  config = mergeConfig(this.defaults, config);

  // 设定请求方法
  // 如果我们传入的配置对象中指定了请求方法,则按这个指定的方法发送相应请求
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    // 如果我们没有指定请求方法,且默认配置中指定了请求方法,则按默认配置中的请求方法发送相应请求
    config.method = this.defaults.method.toLowerCase();
  } else {
    // 如果传入的自定义配置和默认配置中都没有指定,那么就默认发送 get 请求
    config.method = 'get';
  }

 // 创建拦截器中间件,第一个元素是一个函数,用来发送请求;第二个为 undefined(用来补位)
  var chain = [dispatchRequest, undefined];
  // 创建一个 promise 对象,且其状态一定是成功的,同时其成功的值为合并后的请求配置对象
  var promise = Promise.resolve(config);

  // 下面是是有关拦截器的相关设置,暂且不做讨论
  ......

  // 如果链条长度不为 0
  while (chain.length) {
    // 依次取出 chain 的回调函数, 并执行
    promise = promise.then(chain.shift(), chain.shift());
  }

  // 最后返回一个Promise类型的对象
  return promise;
};

得到的信息:

  1. 根据使用axios发送请求的参数进行配置具体的发送方法
  2. 最终是通过dispatchRequest函数进行发送

3.1 dispatchRequest 函数

// \node_modules\axios\lib\core\dispatchRequest.js

// 使用配置好的适配器发送一个请求
module.exports = function dispatchRequest(config) {
  //如果被取消的请求被发送出去, 抛出错误
  throwIfCancellationRequested(config);

  //确保头信息存在
  config.headers = config.headers || {};

  // 对请求数据进行初始化转化
  config.data =
    transformData();
    // ......

  // 合并一切其他头信息的配置项
  config.headers = utils
    .merge
    // ......
    ();

  // 将配置项中关于方法的配置项全部移除,因为上面已经合并了配置
  utils
    .forEach
    // ......
    ();

  // 获取适配器对象 http  xhr
  var adapter = config.adapter || defaults.adapter;

  // 发送请求, 返回请求后 promise 对象  ajax HTTP
  return adapter(config).then(
    function onAdapterResolution(response) {
      // 如果 adapter 中成功发送AJAX请求,则对响应结果中的数据做一些处理并返回成功的Promise
      // ......
      // 设置 promise 成功的值为 响应结果
      return response;
    },
    function onAdapterRejection(reason) {
      // 如果 adapter 中发送AJAX请求失败,则对失败原因做些处理并返回失败的Promise
      // ......
      // 设置 promise 为失败, 失败的值为错误信息
      return Promise.reject(reason);
    }
  );
};

得到的信息:

  1. 根据适配器adapter根据环境发送ajax或者http请求。
  2. 请求发送返回的结果是Promise实例对象

3.3 xhrAdapter 函数

由于源代码代码过长,展示部分截图:

image.png

image.png

image.png

功能:发送XMLHttpRequest请求,并处理请求的各种事件和状态,包括超时、网络错误、请求取消等,最终返回一个Promise对象。

3.4 发送请求的流程

6b8f64561a2349478f70f16ef9c02032~tplv-k3u1fbpfcp-zoom-1.webp

4.axios 拦截器工作原理

先回忆一下拦截器的基本使用:

// 请求拦截器1
axios.interceptors.request.use(
  function (config) {
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);

// 响应拦截器1
axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    return Promise.reject(error);
  }
);

4.1 interceptors.request 和 interceptors.response 两个属性

前面讨论  axios  创建实例的过程时,在  Axios.js  这个文件的构造函数  Axios(){...}  中,源码往实例对象身上添加了  interceptors  这个对象属性。而这个对象属性又有  request  和  response  这两个属性。

// \node_modules\axios\lib\core\Axios.js
// ......

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  //实例对象上有 interceptors 属性用来设置请求和响应拦截器
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager(),
  };
}
// ......

4.2 InterceptorManager 构造函数

而这两个属性都是由  InterceptorManager()  构造函数创建出来的实例对象。use()  函数则是  InterceptorManager  原型对象身上的一个函数属性,所以  request  和  response  这两个属性才可以调用该函数。

// \node_modules\axios\lib\core\InterceptorManager.js
// ......
function InterceptorManager() {
  // 创建一个handlers属性,用于保存 use() 函数传进来的两个回调函数参数
  this.handlers = [];
}

// 添加拦截器到栈中, 以待后续执行, 返回拦截器的编号(编号为当前拦截器综合数减一)
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
  });
  return this.handlers.length - 1;
};
// ......

4.3 use() 函数

而  use()  函数的作用就是将传入  use()  的两个回调函数  fulfilled  和  rejected  包装在一个对象中(这个对象就算是一个拦截器),再保存在  InterceptorManager  实例对象(也就是上面提到的  request  或  response  对象属性)身上的  handlers  数组上。

每调用一次  use()  函数都会在相应的  request  或  response  对象属性身上压入一个包含一对回调函数的对象

4.4 在 Axios.prototype.request 函数中遍历实例对象的拦截器并压入 chain

我们知道,给某个请求添加拦截器(无论是响应拦截器还是请求拦截器)都是在发送请求之前进行的操作。所以此时就又要回到  \node_modules\axios\lib\core\Axios.js  中的  Axios.prototype.request  函数。

// \node_modules\axios\lib\core\Axios.js

// 这个函数是用于发送请求的中间函数,真正发送AJAX请求的操作是被封装在
// \node_modules\axios\lib\adapters\xhr.js 这个文件中的
Axios.prototype.request = function request(config) {
  if (typeof config === "string") {
    // ......
  }
  config = mergeConfig(this.defaults, config);
  if (config.method) {
    // ......
  }

  // 创建拦截器中间件,第一个元素是一个函数,用来发送请求;第二个为 undefined(用来补位)
  var chain = [dispatchRequest, undefined];
  // 创建一个成功的 promise 且其结果值为合并后的请求配置
  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);
  });

  // 如果链条长度不为 0
  while (chain.length) {
    // 依次取出 chain 的回调函数, 并执行
    promise = promise.then(chain.shift(), chain.shift());
  }

  // 最后返回一个Promise类型的对象
  return promise;
};

得到的信息:

  1. 我们设置拦截器的时候会传递两个回调函数fulfilled  和  rejectedAxios实例对象上有 interceptor属性,可以进行设置相应的拦截器,无论是请求还是响应拦截,都是同一个InterceptorManager类进行操作的。
  2. InterceptorManager类的use方法里的handlers属性保存两个回调函数fulfilled  和  rejected的对象。
  3. 我们还记得config吗?它的传递实际上是通过会通过Promise的链式调用传递config参数,所以我们可以看到Promise.resolve(config),那chain变量是怎么回事呢?它负责发送请求,返回一个Promise对象,等会它会被拦截器"夹在"中间
  4. 然后遍历拦截器,unshift()方法使得请求拦截器添加到chain头部,push()方法会使得响应拦截器添加到chain尾部,如此一来,一条链子就穿好了嘿嘿。
  5. 最后通过Promise 的链式调用,将拦截器的一对回调函数fulfilled  和  rejected[dispatchRequest, undefined]弹出,那么就完美的实现了拦截器的功能!

4.5、大致流程图&拦截器工作先后顺序

大概流程如下图所示:

image.png

以上图示来自尚硅谷相关课程

注意,对于请求拦截器:上面的【请求拦截器 1】是在【请求拦截器 2】之前被压入  chain  数组头部的,但是【请求拦截器 2】的下标位置比较靠前,这也是为什么*【请求拦截器 2】中的回调会先被执行*。

而对于响应拦截器:上面的【响应拦截器 1】是在【响应拦截器 2】之前被压入  chain  数组尾部的,但是【响应拦截器 1】的下标位置比较靠前,这也是为什么*【响应拦截器 1】中的回调会先被执行*。

观察下面代码的输出结果:

// ......
// 发送请求的函数
function dispatchRequest(config) {
  console.log("发送请求");
  return new Promise((resolve, reject) => {
    resolve({
      status: 200,
      statusText: "OK",
    });
  });
}
// ......
// 设置两个请求拦截器
axios.interceptors.request.use(
  function one(config) {
    console.log("请求拦截器 成功 - 1号");
    return config;
  },
  function one(error) {
    console.log("请求拦截器 失败 - 1号");
    return Promise.reject(error);
  }
);

axios.interceptors.request.use(
  function two(config) {
    console.log("请求拦截器 成功 - 2号");
    return config;
  },
  function two(error) {
    console.log("请求拦截器 失败 - 2号");
    return Promise.reject(error);
  }
);

// 设置两个响应拦截器
axios.interceptors.response.use(
  function (response) {
    console.log("响应拦截器 成功 1号");
    return response;
  },
  function (error) {
    console.log("响应拦截器 失败 1号");
    return Promise.reject(error);
  }
);

axios.interceptors.response.use(
  function (response) {
    console.log("响应拦截器 成功 2号");
    return response;
  },
  function (error) {
    console.log("响应拦截器 失败 2号");
    return Promise.reject(error);
  }
);

// 发送请求
axios({
  method: "GET",
  url: "http://localhost:3000/posts",
}).then((response) => {
  console.log(response);
});

// 输出结果----->
// 请求拦截器 成功 - 2号
// 请求拦截器 成功 - 1号
// 发送请求
// 响应拦截器 成功 1号
// 响应拦截器 成功 2号

5. axios 的取消过程

先回忆一下基本用法:

// 方式一
const cancelToken = new axios.CancelToken((cancel) => {
  // 创建一个 CancelToken 类实例
  // 在这里执行取消操作
  setTimeout(() => {
    console.log("取消请求");
    cancel("用户手动取消");
  }, 1000);
});

// 发起 GET 请求
axios
  .get("https://jsonplaceholder.typicode.com/posts", {
    cancelToken: cancelToken,
  })
  .then((response) => console.log(response.data))
  .catch((error) => {
    if (axios.isCancel(error)) {
      console.log("请求已被取消:", error.message);
    } else {
      console.error(error);
    }
  });

// 方式二
const source = axios.CancelToken.source(); // 创建一个 CancelToken

// 发起 GET 请求
axios
  .get("https://jsonplaceholder.typicode.com/posts", {
    cancelToken: source.token,
  })
  .then((response) => console.log(response.data))
  .catch((error) => {
    if (axios.isCancel(error)) {
      console.log("请求已被取消:", error.message);
    } else {
      console.error(error);
    }
  });

// 取消请求
source.cancel("用户取消了请求");

5.1 取消 axios 请求的底层代码

// \node_modules\axios\lib\adapters\xhr.js
// ......
//如果我们发送请求时传入的配置对象中配置了 cancelToken,则调用 then 方法设置成功的回调
if (config.cancelToken) {
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
      return;
    }
    //取消请求
    request.abort();
    reject(cancel);
    request = null;
  });
}
// ......

得到的信息:

  1. 实际上就是调用了  XMLHttpRequest  对象身上的  abort()  方法
  2. 只有当config.cancelToken.promise这个Promise对象的状态转为fulfilled了才能执行  abort()  方法
  3. 我们可以推测一定是在外面调用了该函数,状态才改变了

5.2 cancelToken 构造函数

function CancelToken(executor) {
  //执行器函数必须是一个函数
  if (typeof executor !== "function") {
    throw new TypeError("executor must be a function.");
  }

  //声明一个变量
  var resolvePromise; //  resolvePromise()
  //实例对象身上添加 promise 属性
  this.promise = new Promise(function promiseExecutor(resolve) {
    //将修改 promise 对象成功状态的函数暴露出去
    resolvePromise = resolve;
  });
  // token 指向当前的实例对象
  var token = this;
  //将修改 promise 状态的函数暴露出去, 通过 cancel = c 可以将函数赋值给 cancel
  executor(function cancel(message) {
    if (token.reason) {
      // 已经申请取消
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel,
  };
};

分析取消 axios 的流程:

  1. 方法一:创建一个CancelToken类实例,参数cancel就是cancelToken函数里参数executor的回调函数,当执行cancel('用户手动取消')函数时,也就是执行了回调函数,那么this.promise的状态就改变了。
  2. 方法二:调用axios.CancelToken.source(),返回值是一个对象,对象的属性是新创建的CancelToken 实例和其参数executor的回调函数,后续的流程和方式一一样。
  3. 需要特别注意的是:取消axios必须在config里配置cancelToken,因为传入的配置对象中配置了 cancelToken才会执行下一步的可能;其次就是大家有没有注意到cancelToken函数里定义的 token.reason,可以防止多次取消。

c1afc0fc153e43d2a8e1c57d31596341~tplv-k3u1fbpfcp-watermark.png