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

154 阅读5分钟

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

前言

大家好,欢迎来到今天的axios的源码学习。在上一个篇章中我们完成了拦截器功能的相关实现。今天我们要完成的目标是在发送请求之后如何取消请求,并能写出相关的源码逻辑。

目标

取消请求[00_00_01--00_00_21].gif

可以看到当我们点击蓝色按钮向本地服务器发送请求后,快速点击黄色按钮取消了这次请求

实现步骤

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

  • 创建Axios构造函数

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

  • 创建dispatchRequest函数

  • 创建xhrAdaptert函数并发送请求

  • 创建axios()函数

  • 创建CancelToken函数

  • 点击按钮发送并取消请求

具体功能实现

书写功能测试代码

html文本部分

引入以下样式用来美化按钮

<link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

按钮

 <div class="container">
        <h2 class="page-header">axios取消请求</h2>
        <button class="btn btn-primary"> 发送请求 </button>
        <button class="btn btn-warning"> 取消请求 </button>
 </div>

script模块基本代码

//获取按钮 以上为模拟实现的代码
    const btns = document.querySelectorAll('button');
    //2.声明全局变量
    let cancel = null;
    //发送请求
    btns[0].addEventListener('click', function () {
      //检测上一次的请求是否已经完成
      if (cancel !== null) {
        //取消上一次的请求
        cancel();
      }
      //创建 cancelToken 的值
      let cancelToken = new CancelToken(

        function (c) {
          cancel = c;
        }
      );
      axios({
        method: 'GET',
        url: 'http://localhost:3000/posts',
        //1. 添加配置对象的属性
        cancelToken: cancelToken
      }).then(response => {
        console.log(response);
        //将 cancel 的值初始化
        cancel = null;
      })
    })
    //绑定第二个事件取消请求
    btns[1].addEventListener('click', function () {
      cancel();
    }) 

可以看到在script模块中我们获取了两个按钮,当点击第一个按钮我们发送请求,其内部的回调函数中创建了一个cancelToken对象,并将这个cancelToken放置在了axios请求的内部作为参数。而点击第二个按钮我们只是执行了一个cancel函数,当然现在里面的很多函数和对象还不存在,下面我们将逐渐完善。

创建JSON文件,开启服务器

此过程与前文一致,在此不做赘述

创建Axios构造函数

  // Axios构造函数
    function Axios(config) {
      this.config = config;
    }

挂载request方法

  Axios.prototype.request = function (config) {
      return dispatchRequest(config);
    }

可以看到我们在Axios的原型上挂载了一个request函数,内部返回了dispatchRequest函数,并接受上面传递下来的参数参数config

创建dispatchRequest函数

  //dispatchRequest 函数
    function dispatchRequest(config) {
      return xhrAdapter(config);
    }

这一步中我们创建了dispatchRequest函数,其内部返回xhrAdapter函数,并接受上面传递下来的参数参数config。

创建xhrAdapter函数

xhrAdapter函数的返回值必须是一个promise类型的值,并要在调用原生内置的XMLHttpRequest的实例化对象完成发送请求,此操作在前文中书写并解释过,在此不做过多阐述

  function xhrAdapter(config) {
      //发送 AJAX 请求
      return new Promise((resolve, reject) => {
        //实例化对象
        const xhr = new XMLHttpRequest();
        //初始化
        xhr.open(config.method, config.url);
        //发送
        xhr.send();
        //处理结果
        xhr.onreadystatechange = function () {
          if (xhr.readyState === 4) {
            //判断结果
            if (xhr.status >= 200 && xhr.status < 300) {
              //设置为成功的状态
              resolve({
                status: xhr.status,
                statusText: xhr.statusText
              });
            } else {
              reject(new Error('请求失败'));
            }
          }
        }      
        
      })
    }

可以看到xhrAdapter函数主要作用之一就是发送请求,并返回一个promise类型的值,最终返回给request,也就是axios执行的结果

创建axios函数

//创建 axios 函数
    const context = new Axios({});
    const axios = Axios.prototype.request.bind(context);

此步操作在前面的文章中都有涉及并讲解,在此不做赘述

创建CancelTok函数

这一步操作是我们今天要理解并实现的核心代码。由于我们在功能测试代码中的axios( { ... })传入了cancelToken参数。而这个cancelToken是通过new CancelToken构造函数而来的,因此我们现在要创建一个CancelToken函数,其接收一个参数executor

image.png

在CancelTok函数内部我们要申明一个变量resolvePromise供我们后期使用。其次我们要申明一个变量promise,接收来自原生内置Promise函数实例化对象的结果

image.png

可以看到在new Promise的内部,我们调用里面的函数,其执行结果是把resolve参数赋值给了刚刚申明的变量resolvePromise

注意:此时参数resolve是一个函数,可以使得这个变量变为一个成功的promise函数

关于executor : 当我们new CancelToken之后,它会往CancelToken函数中传入一个参数,这个参数就是一个函数,如图

image.png

因此我们把一个函数作为参数传入给了这个excutor函数,如图

image.png

这一步中,我们执行了executor函数,执行的结果就是将这个传递过来的函数赋值给了cancel

一定要注意此时只是executor函数执行,而传递过来的function函数并未执行

代码

//CancelToken 构造函数
    function CancelToken(executor) {
      //声明一个变量
      var resolvePromise;
      //为实例对象添加属性
      this.promise = new Promise((resolve) => {
        //将 resolve 赋值给 resolvePromise
        resolvePromise = resolve
      });
      //调用 executor 函数
      executor(
        function () {
        resolvePromise();
      });
    }

完善xhrAdapter函数

接下来我们要完成xhrAdapter函数,当发送请求后我们如果想快速终止这次请求,其核心是调用xhr身上的abort( )函数

在进入到这一步之前,我们要判断config配置信息中是否携带有cancelToken这个对象,如果有,再往下继续;如果没有,直接不进行此步操作。

image.png

可以看到,在这个处理中我们会检查config.cancelToken.promise的状态是否为成功,如果成功则直接调用abort( )函数,终止请求,如果携带了cancelToken参数而未调用,则promise的值为pending不执行then内部的代码

而如何终止请求发送呢?来看下面

终止请求流程理解

上面我们提到过cancel是一个函数,而如果这个函数一执行,其内部的resolvePromise() 就会执行,而这个函数一执行,就相当于上面的resolve函数执行,那变量promise就必然为一个成功的promise,而此时 config.cancelToken.promise就为一个成功的promise函数,所以then方法就会执行内部的第一个函数,终止请求,可谓牵一发而动全身!

那我们什么时候来执行这个cancel函数呢?那这个执行权当然要交付给我们的用户,因此第二个按钮事件中的回调执行结果就是就是cancel(),点击按钮取消请求

image.png

到此为止我们的逻辑代码全部书写完毕,这里要理解几个核心点

  1. 函数传参的过程,以及不同函数作为参数时的状态
  2. 终止请求的方法是借助了xhr身上的abort( ) 函数
  3. cancel函数的执行,会起连锁反应,影响变量promise的状态

各位在执行时务必要把网速调至为低速3g,如图

image.png

这样执行axios时会执行的稍微慢一些,有充分的时间来点击按钮取消请求查看效果

image.png

总结

至此我们完成了所有关于axios核心功能的逻辑实现,分别是

  • axios发送请求的测试实现
  • axios发送请求本地服务器实现
  • axios拦截器的相关实现
  • axios请求取消的实现

关于axios的源码理解与逻辑实现到此告一段落,谢谢大家

本节代码

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title> axios 的由来</title>
</head>

<body>
  <script>
    // console.log(axios);
    // axios();
    // axios.get();
    // axios.post();

    //构造函数
    function Axios(config) {
      //初始化
      this.defaults = config;//为了创建 default 默认属性
      this.intercepters = {
        request: {},
        response: {}
      }
    }
    //原型添加相关的方法
    Axios.prototype.request = function (config) {
      console.log('发送 AJAX 请求 请求的类型为 ' + config.method);
    }
    Axios.prototype.get = function (config) {
      return this.request({ method: 'GET' });
    }
    Axios.prototype.post = function (config) {
      return this.request({ method: 'POST' });
    }

    //声明函数
    function createInstance(config) {
      //实例化一个对象
      let context = new Axios(config);// context.get()  context.post()  但是不能当做函数使用 context() X
      //创建请求函数
      let instance = Axios.prototype.request.bind(context);// instance 是一个函数 并且可以 instance({})  此时 instance 不能 instance.get X
      //将 Axios.prototype 对象中的方法添加到instance函数对象中
      Object.keys(Axios.prototype).forEach(key => {
        instance[key] = Axios.prototype[key].bind(context);// this.default  this.interceptors
      });
      //为 instance 函数对象添加属性 default 与 interceptors
      Object.keys(context).forEach(key => {
        instance[key] = context[key];
      });
      // console.dir(instance)
      return instance;
    }

    let axios = createInstance();
    //发送请求
    axios({method:'POST'});
    axios.get({});
    axios.post({});

  </script>
</body>

</html>