axios核心源码逻辑实现(三)

198 阅读7分钟

axios核心源码逻辑实现(三)

前言

大家好,欢迎来到今天的axios的源码学习。在上一个篇章中我们完成了给本地服务器发送请求并获取数据的操作。今天我们要完成的目标是实现axios请求拦截器的功能,具体要明白axios拦截器的执行顺序以及如何实现。

目标

如图,我们在执行axios发起请求时会打印拦截器里的内容

image.png

打印结果:

image.png

可以看到拦截器执行的顺序是先执行请求拦截器-2号,再执行请求拦截器-1号,再执行响应拦截器1号,再执行响应拦截器2号,简单概括之,请求拦截器先进后出,响应拦截器先进先出

实现步骤

  • 创建JSON文件,开启服务器

  • 创建Axios构造函数

  • 创建InterceptorManager函数

  • 在Axios原型上挂载request方法,内部完成相关业务逻辑

  • 借助request函数创建axios函数

  • 声明dispatchRequest函数

  • 在InterceptorManager原型上挂载use函数

  • 调用拦截器执行axios,并发送请求

具体功能实现

书写功能测试代码

 //以下为功能测试代码
    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 three(response) {
      console.log('响应拦截器 成功 1号');
      return response;
    }, function (error) {
      console.log('响应拦截器 失败 1号')
      return Promise.reject(error);
    });

    axios.interceptors.response.use(function four(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);
    });

各位可以先将上面这些代码cv到自己的文件中,稍后将会用到

创建一个JSON文件,并开启服务器

这里创建并运行JSON文件开启本地服务器的流程与上一篇文章相同,在此不过多赘述

创建Axios构造函数

接着按照惯例我们创建Axios构造函数

image.png

代码

    function Axios(config) {
      this.config = config
      this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()  //注意虽然new的同一个函数,但此时request和response的地址不一样
      }
    }

注意此时我们还未创建InterceptorManager函数,稍后new完InterceptorManager函数后,request和response就是两个地址不一样的对象

创建InterceptorManager函数

image.png

代码

  // InterceptorManager的构造函数
  function InterceptorManager() {
    this.handlers = []
  }

可以看到每当我们new了一个InterceptorManager的实例之后,实例身上就添加了一个handlers属性,值为一个数组

Axios原型挂载request方法

这一步我们需要在这个request函数体内声明一个成功的promise函数,并创建一个chains数组功能后期使用

image.png

此时变量promise就是一个成功的promise函数

接着我们申明dispatchRequest函数,其内部要返回一个成功的promise函数,至于为什么要返回成功的promise,本文末尾将会进行讲解

创建dispatchRequest函数

   function dispatchRequest() {
      return new Promise((resolve, reject) => {
        resolve({
          status: 200,
          statusGText: 'OK'
        })
      })
    }

dispatchRequest函数创建完毕,此时上面的chains[ 0 ]就是一个函数

创建axios函数


    let context = new Axios({})

    let axios = Axios.prototype.request.bind(context)

    Object.keys(context).forEach(item => {
      axios[item] = context[item]
    })

这里简单理解一下,第一步new了一个context对象,第二步借助request函数调用bind方法创建axios函数,第三步将context身上的属性和方法挂载到axios函数中,此时axios身上就多了config和interceptors属性,注意要把这些代码要紧挨着刚才cv的功能测试代码

在InterceptorManager原型上挂载use函数

在InterceptorManager的use函数接收两个参数,分别是拦截器中第一个函数和第二个函数。我们将向实例对象身上的handlers数组中推入一个元素,这个元素就是这两个函数。也就是说每次调用use方法都会推一个对象进去,因为在功能函数汇中我们调用了两次use,所以此时handlers中一共有两个对象,四个函数

image.png

代码

// InterceptorManager原型上挂载use函数,接收两个函数作为参数
  InterceptorManager.prototype.use = function (fulfilled, rejected) {
    this.handlers.push({ fulfilled, rejected })
  }

此时由于我们调用了use函数,所有axios

request函数的完善

接着我们继续完善Axios原型上的request函数,函数内部要完成对实例化对象axios身上interceptors中request属性和response里的handler函数进行遍历,并添加至chains数组中

image.png

注意此时在对request.handlers进行遍历时,调用的是unshift函数,也就是把handlers数组的第一次调用request.use()中传递的两个函数放入到了chains最前面,第二次调用request.use()中再放至第一次的最前面,所以稍后的执行顺序是先进来的后执行。 这里我们可以做一次打印

image.png

打印结果

image.png

而响应拦截器是往chains数组后面push元素,因此响应拦截器中传递的参数在chains数组中的位置是依次的,所以稍后执行顺序是先进来的先执行

image.png

打印结果

image.png

两次打印结果都符合预期

接下来我们要完成对这样一个操作,利用while循环,内部调用promise.then方法执行chains数组中的元素( 函数 )

image.png

代码

   //遍历
      while (chains.length > 0) {
        promise = promise.then(chains.shift(), chains.shift());
      }

可以看到由于变量promise函数是一个成功的promise函数,因此它会执行then方法里的第一个参数,也就是chains里的第一个元素。这里要理解,不管then方法是否调用,只要内部存在Array.shift( )方法,都会立即返回被删除的元素( 此时元素本身并没有立即执行),并使得Array.length的长度减少。 这里一次性调用了两次shift( ) ,也就是说执行then之后,chains数组一次性少两个。而当元素全部执行完毕后,会返回一个promise类型的值,这个值就是request函数执行的结果,也就是下面axios({...} )执行完后的结果

这里要注意因为then调用了chains数组中的第一个元素,所以chains[ 0 ]立即执行,打印出相应的内容

dispatchRequest与undefined

一直以来困扰着小伙伴们的一个问题,为什么一开始要往chains数组里初始化dispatchRequest函数与undefined这两个值呢?

这里解答一下,当我们调用了相应的拦截器,当检测到while内部循环中有失败的promise时,它后面循环时总时会跳到undefined,而undefined会被then方法捕捉到,造成异常穿透,最终会使得axios( ) 执行结果为一个失败的promise函数,符合情理。而如果此时没有调用拦截器,只是调用了axios( {...} ) 函数,假设此时url和method都正确,它执行到后面只会执行dispatchRequest函数,而dispatchRequest函数执行的结果是一个成功的promise函数,因此axios执行的结果就是一个成功的promise( )函数,亦符合情理

调用拦截器执行axios,发送请求

此时我们代码已经整个完成,测试功能代码看打印结果,结果如下

image.png

这样一来就完成了我们拦截器的相关的功能,一个是在请求发送之前,或者在响应回来之前做些准备工作。第二个是实现了多个请求拦截器执行顺序为先进后出,而多个响应拦截器执行顺序为先进先出

总结

今天我们完成了拦截器相关的操作,里面涉及到许多方面的知识点,如原型的认知,promise的理解,数组的方法等等及预处理等等,在下一篇章中我们将讲解关于如何取消请求的方法

本节代码

代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>

    function Axios(config) {
      this.config = config
      this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()  //注意虽然new的同一个函数,但此时request和response的地址不一样
      }
    }

   Axios.prototype.request = function (config) {
      //创建一个 promise 对象
      let promise = Promise.resolve(config);
      //创建一个数组
      const chains = [dispatchRequest, undefined];
      //处理拦截器
      //请求拦截器 将请求拦截器的回调 压入到 chains 的前面  request.handles = []
      this.interceptors.request.handlers.forEach(item => {
        chains.unshift(item.fulfilled, item.rejected);
      });

   
      //响应拦截器
      this.interceptors.response.handlers.forEach(item => {
        chains.push(item.fulfilled, item.rejected);
      });

      // console.log(chains);
      //遍历
      while (chains.length > 0) {
        promise = promise.then(chains.shift(), chains.shift());
      }

      return promise;
    }

    function dispatchRequest() {
      return new Promise((resolve, reject) => {
        resolve({
          status: 200,
          statusGText: 'OK'
        })
      })
    }



  // InterceptorManager的构造函数
  function InterceptorManager() {
    this.handlers = []
  }

// InterceptorManager原型上挂载use函数,接收两个函数作为参数
  InterceptorManager.prototype.use = function (fulfilled, rejected) {
    this.handlers.push({ fulfilled, rejected })
  }


    let context = new Axios({})

    let axios = Axios.prototype.request.bind(context)

    Object.keys(context).forEach(item => {
      axios[item] = context[item]
    })


    //以下为功能测试代码
    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 three(response) {
      console.log('响应拦截器 成功 1号');
      return response;
    }, function (error) {
      console.log('响应拦截器 失败 1号')
      return Promise.reject(error);
    });

    axios.interceptors.response.use(function four(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);
    });
  </script>
</body>

</html>