美文网首页
[源码] axios

[源码] axios

作者: woow_wu7 | 来源:发表于2021-09-23 08:40 被阅读0次
    image
    image
    • 下图为 2020/01/05 复习时重新绘制
    • 下图为 2020/01/05 复习时重新绘制


      image
    • 下图为 2021/02/07 再复习请求流程


      image

    导航

    [深入01] 执行上下文
    [深入02] 原型链
    [深入03] 继承
    [深入04] 事件循环
    [深入05] 柯里化 偏函数 函数记忆
    [深入06] 隐式转换 和 运算符
    [深入07] 浏览器缓存机制(http缓存机制)
    [深入08] 前端安全
    [深入09] 深浅拷贝
    [深入10] Debounce Throttle
    [深入11] 前端路由
    [深入12] 前端模块化
    [深入13] 观察者模式 发布订阅模式 双向数据绑定
    [深入14] canvas
    [深入15] webSocket
    [深入16] webpack
    [深入17] http 和 https
    [深入18] CSS-interview
    [深入19] 手写Promise
    [深入20] 手写函数

    [react] Hooks

    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

    [源码-webpack01-前置知识] AST抽象语法树
    [源码-webpack02-前置知识] Tapable
    [源码-webpack03] 手写webpack - compiler简单编译流程
    [源码] Redux React-Redux01
    [源码] axios
    [源码] vuex
    [源码-vue01] data响应式 和 初始化渲染
    [源码-vue02] computed 响应式 - 初始化,访问,更新过程
    [源码-vue03] watch 侦听属性 - 初始化和更新
    [源码-vue04] Vue.set 和 vm.$set
    [源码-vue05] Vue.extend

    [源码-vue06] Vue.nextTick 和 vm.$nextTick

    前置知识

    一些单词

    Interceptors:拦截器
    bother:烦恼
    ties:系,捆,领带,联系
    
    immutably:不变的
    precedence:优先的
    
    determine:确定,查明
    ( Determine if a value is a FormData 确定值是否是FormDate类型 )
    
    inherit:继承
    

    <font color=DarkOrchid>axios取消请求方法1 - axios.CancelToken.source()</font>

    • const source = axios.CancelToken.source() 工厂函数
      • source.token
      • source.cancel(message)
      • source = {token: cancelToken对象, cancel: 取消函数}
    • axios.isCancel
    • 注意
      • 每次请求的source不能是同一个source对象,如果是同一个source对象,就会出现取消请求后,不能再次发送请求的情况
    (1) 取消请求后,不能再次发送请求
    const CancelToken = axios.CancelToken;
    const source = CancelToken.source(); // ----------------------------- source工厂函数
    
    axios.get('/user/12345', {
      cancelToken: source.token 
      // ------------------------------ token [2021/01/04更新]
      // ------------------------------ token,这里取消请求后,就不能再次发送请求了,因为是同一个source对象,即同一个token
      // ------------------------------ 解决办法就是把 source 的生成方法每个请求方法中
    }).catch(function (thrown) {
      if (axios.isCancel(thrown)) {
        console.log('Request canceled', thrown.message);
      } else {
        // handle error
      }
    });
    
    // 取消请求
    source.cancel('Operation canceled by the user.'); // ----------------- cancel函数
    

    2021/01/04 复习时发现上面的请求,取消请求后不能再次发送请求,用如下方法解决

    (2) 取消请求后,可以再次发送请求
    import React, { useState } from 'react'
    import axios from 'axios'
    
    const AxiosCancelToken = () => {
      const [cancalFn1, setCancalFn1] = useState(() => { }) as any
      const [links] = useState([
        {
          name: 'axios源码 - axios源码分析仓库',
          url: 'https://github.com/woow-wu7/7-react-admin-ts/tree/master/src/SOURCE-CODE-ANALYSIS/AXIOS'
        },
        {
          name: 'axios源码 - 我的掘金博客',
          url: 'https://juejin.cn/post/6844904147532120072'
        },
      ])
      const renderLinks = () => links.map(({ name, url }) => <div key={name}><a href={url} target="blank">{name}</a></div>)
      // axios取消请求
      // 方法一
      // axios.CancelToken.source.token
      // axios.CancelToken.source.cancel
      // 注意点:设个source不能是同一个source,如果是同一个source的话,取消请求后就不能再次重新请求了
      const handleRequest = async () => {
        const source = axios.CancelToken.source()
        setCancalFn1(() => source.cancel)
        await new Promise(resolve => {
          setTimeout(() => {
            console.log('延时2s执行')
            return resolve('success')
          }, 2000)
        })
        await axios({
          url: '/API/pic.php',
          method: 'get',
          cancelToken: source.token
        }).catch(err => {
          if (axios.isCancel(err)) {
            console.log('object :>> ', err.message);
          } else {
            console.log('error')
          }
        })
      }
      const cancelRequest = () => {
        cancalFn1('请求取消了')
      }
      return (
        <div className="axios-cancel-token">
          <p>Axios - CancelToken测试</p><br />
    
          <div>请打开浏览器调试面板调式</div>
          <button onClick={handleRequest}>点击发送请求1 - 每次请求都用promise延时2s模拟</button><br />
          <button onClick={cancelRequest}>点击 - 取消请求方法1 - 工厂函数source</button><br />
          <div>
            {renderLinks()}
          </div>
        </div>
      )
    }
    
    export default AxiosCancelToken
    

    <font color=DarkOrchid>axios取消请求方法2 - new axios.CancelToken()</font>

    • const cancalTokenInstance = new axios.CancelToken(c => cancel = c)
      • 生成的是 cancalToken 实例
      • 参数是一个回调函数,回调函数的参数就是 cancel() 函数
      • 注意传入CancelToken(c => cancel = c)是一个cancel函数,这个cancel函数将自己的参数c赋值给了cancel变量,c也是一个函数,即cancel函数
    const CancelToken = axios.CancelToken;
    let cancel;
    
    axios.get('/user/12345', {
      cancelToken: new CancelToken(function executor(c) {
        // An executor function receives a cancel function as a parameter
        cancel = c;
      })
    });
    
    // cancel the request
    cancel();
    

    2020/01/06 复习第二种取消请求的例子,这里面有一个坑,请看下面代码的注释

    • 1.通过直接new axios.CancelToken()
    • 2.而不是调用CancelToken的静态方法source()生成{token, cancel}
     const handleRequest2 = async () => {
        const token = new axios.CancelToken(c => setCancelFn2(() => c)) // 将c赋值给state
        await new Promise(resolve => {
          setTimeout(() => {
            console.log('延时2s执行')
            return resolve('success')
          }, 2000)
        })
        await axios({
          url: '/API/pic.php',
          method: 'get',
          cancelToken: token,
          // cancelToken: new axios.CancelToken(c => setCancelFn2(() => c)) // 这种写法是不可以的!!!!!!!!!!!!!!!!!!!!
        }).catch(err => {
          if (axios.isCancel(err)) {
            console.log('object :>> ', err.message);
          } else {
            console.log('error')
          }
        })
      }
      const cancelRequest2 = () => {
        console.log(cancelFn2);
        cancelFn2('请求取消了') // state
      }
    

    <font color=DarkOrchid>axios取消请求 - 实例1</font>

    <template>
      <div class="cancal-request">
        <div>cancel-request</div>
        <div @click="getData">请求:点击获取请求数据 - new CancelToken()方法</div>
        <div @click="cancalRequest">取消请求:点击取消获取请求数据</div>
    
        <div @click="getData2">请求:点击获取请求数据 - source()工厂函数方式</div>
        <div @click="cancalRequest2">取消请求:点击取消获取请求数据</div>
    
        <div>{{data.responseTime}}</div>
      </div>
    </template>
    
    <script>
    import axios from "axios";
    
    export default {
      name: "CancelRequest",
      data() {
        return {
          cancelFn: null,
          data: {
            responseTime: null
          },
          source: null
        };
      },
      methods: {
        getData() {
          axios({
            baseURL: "/api",
            timeout: 2000,
            url: "/cancel-request",
            method: "get",
            cancelToken: new axios.CancelToken(c => {
              // new axios.CancelToken() 
                // 返回值  => 是一个 cancelToken
                // 参数 => 是一个回调函数,回调函数的参数是 cancel() 函数
              this.cancelFn = c;
            })
          }).then(res => {
            console.log(res, "res-data");
            this.data = res.data
          });
        },
        cancalRequest() {
          if (this.cancelFn) {
            this.cancelFn('以 new axios.CancelToken(c => this.cancelFn = c) 方式取消请求');
            // 取消请求
          }
        },
        getData2() {
          this.source = axios.CancelToken.source();
          // 利用 axios.CancelToken.source() 工厂函数生成一个对象
            // source = {token: cancelToken对象, cancel: 取消函数}
            // source.token
            // source.cancel
          axios({
            baseURL: "/api",
            timeout: 2000,
            url: "/cancel-request",
            method: "get",
            cancelToken: this.source.token // token
          }).then(res => {
            console.log(res, "res-data");
            this.data = res.data
          });
        },
        cancalRequest2() {
          if (this.source.cancel) {
            this.source.cancel('以 source()工厂函数方法 取消请求');  // cancel
          }
        }
      }
    };
    </script>
    
    

    <font color=DarkOrchid>axios取消请求 - 实例2 - mixin方式</font>

    mixin/index.js
    
    import axios from 'axios';
    
    export const mixinCancelToken = {
      data() {
        return {
          cancelToken: null, // ----------------------------------- cancelToken
          cancel: null // ----------------------------------------- cacel取消函数
        }
      },
      created() {
        this.cancelToken = new axios.CancelToken(c => { // -------- cancelToken赋值
          this.cancel = c // -------------------------------------- cancel函数赋值
        })
      },
      beforeDestroy() {
        if (this.cancel) {
          this.cancel(`取消请求mixin方式`) // --------------------- 组件卸载时,取消请求
        }
      }
    }
    
    
    业务组件中
    
    <template>
      <div class="cancal-request-component">
        <div>cancel-request-component</div>
        <div @click="getData">请求:点击获取请求数据 - mixin方式</div>
        <div @click="cancalRequest">取消请求:点击取消获取请求数据 - - mixin方式</div>
      </div>
    </template>
    
    <script>
    import { mixinCancelToken } from "../mixin";
    import axios from "axios";
    
    export default {
      name: "CancelRequestComponent",
      mixins: [mixinCancelToken], // ----------------------------------------- 注入mixin
      data() {
        return {};
      },
      mounted() {},
      methods: {
        getData() {
          axios({
            baseURL: "/api",
            timeout: 2000,
            url: "/cancel-request",
            method: "get",
            cancelToken: this.cancelToken // --------------------------------- 从mixin中获取cancelToken
          }).then(res => {
            console.log(res, "res-data");
            this.data = res.data
          });
        },
        cancalRequest() {
          if (this.cancel) {
            this.cancel('取消请求mixin方式') // ------------------------------- 从mixin中获取cancel
          }
        }
      }
    };
    </script>
    
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped lang="stylus">
    .cancal-request-component {
      background: yellow;
    }
    </style>
    
    

    axios拦截器的使用

    import axios from 'axios'
    
    const baseURL = '/api'
    const headers = {
        'Content-Type': 'application/json'
    }
    
    const axiosInstance = axios.create({
        baseURL,
        headers,
        timeout: 1000,
    })
    console.log(typeof axiosInstance, 'axios.create({...})返回一个函数')
    
    // 请求拦截
    // 1. Axios()构造函数中存在this.interceptors对象
    // 2. interceptors对象上有两个属性
        // request属性,是一个实例对象,继承了原型对象上的 use eject forEach 方法等
        // response属性,是一个实例对象,继承了原型对象上的 use eject forEach 方法等
    axiosInstance.interceptors.request.use(config => {
        return config
    }, error => {
        return Promise.reject(error)
    })
    
    // 响应拦截
    axiosInstance.interceptors.response.use(response => {
        return response
    }, error => {
        return Promise.reject(error)
    })
    
    export const baseApi = async ({ url, method, paramsOrData }) => {
        try {
            const paramsOrDataKey = method.match(/get/i) ? 'params' : 'data';
            const res = await axios.request({
                url,
                method,
                [paramsOrDataKey]: paramsOrData
            })
            return res
        } catch (err) {
            throw new Error(err)
        }
    
    }
    
    export default axiosInstance
    

    XMLHttpRequest

    • 如何获取response???
      • xhr.response
      • xhr.responseText
      • xhr.responseXML
      • xhr.responseText
        • 在 xhr.responseType = 'text' , '',不设置时,xhr实例对象上才有此属性,此时才能调用
      • xhr.response
        • 在 xhr.responseType = 'text' ,'' 时,值是 ( '' )
        • 在 xhr.resposneType 是其他值时,值是 ( null )
    • xhr.responseType
      • text
      • document
      • json
      • blob
      • arrayBuffer
    const xhr = new XMLHttpRequest()
    // new 命令总是返回一个对象,要么是this对象,要么是return后面跟的对象
    // new 调用的是构造函数,说明XMLHttpRequest是一个构造函数
    
    (1) xhr.open()
    - 初始化 HTTP 请求参数,比如url,http请求方法等,但并 ( 不发送请求 )
    - xhr.open() 方法主要供 xhr.send() 方法使用
    
    xhr.open(method, url, async, username, password)
    - 参数
      - method:http请求的方法,包括 GET POST HEAD
      - url:请求的地址
      - async:是否异步
        - true,默认值,异步请求,通常需要调用 onreadystatechange() 方法
        - false,对 send() 方法的调用将阻塞,直到响应完全接收
    
    (2) xhr.send()
    - 发送一个http请求
    
    xhr.send(body)
    - get请求:get请求的参数可以直接写在 open() 方法中
    - post请求:post请求的参数写在 send() 方法中
      - 注意:
        - body参数的数据类型会影响 requestHeader 中的 Content-Type 的默认值,如何手动指定则会覆盖默认值
        - 如果data是 Document 类型,同时也是HTML Document类型,则content-type默认值为text/html;charset=UTF-8;否则为application/xml;charset=UTF-8;
        - 如果data是 DOMString 类型,content-type默认值为text/plain;charset=UTF-8;
        - 如果data是 FormData 类型,content-type默认值为multipart/form-data; boundary=[xxx]
        - 如果data是其他类型,则不会设置content-type的默认值
    
    (3) xhr.setRequestHeader()
    - 指定一个http请求的头部,只有在 readState = 1 时才能调用
    - setRequestHeader可以调用的时机
      - 1. 在 readyStaet = 1 时
      - 2. 在 open() 方法之后,send() 方法之前
      - 3. 其实 1 2 是一个意思
    
    xhr.setRequestHeader('name', 'value')
    - 参数
      - name:头部的名称
      - value:头部的值
    - 注意
      - setRequestHeader() 方法可以 ( 多次调用 ) ,值不是 ( 覆盖override ) 而是 ( 追加append )
      - setRequestHeader() 只有在 readyState = 1 时才能调用,即 open() 方法之后,send() 方法之前
      
    (4) xhr.getResponseHeader()
    - 指定http响应头部的值
    
    (5) xhr.abort()
    - 取消当前响应,关闭连接并且结束任何未决的网络活动
    - xhr.abort()会将 readyState 重置为0
    - 应用:取消请求,在请求耗时太长,响应不再有必要时,调用该方法
    - abort:是终止的意思
    
    (6) xhr.onreadystatecange()
    - 在 readyState 状态改变时触发
    - xhr.onreadystatechange() 在 readyState = 3 时,可能多次调用
    - onreadystatechange 都是小写
    - readyState 驼峰
    
    readyState状态
    0 UNSENT ------------- xhr对象成功构造,open() 方法未被调用
    1 OPEND  ------------- open() 方法被调用,send() 方法未被调用,setRequestHeader() 可以被调用
    2 HEADERS_RECEIVED --- send() 方法已经被调用,响应头和响应状态已经返回
    3 LOADING ------------ 响应体 ( response entity body ) 正在下载中,此状态下通过 xhr.response 可能已经有了响应数据
    4 NODE   ------------- 整个数据传输过程结束,不管本次请求是成功还是失败
    
    (7) xhr.onload
    - 请求成功时触发,此时 readyState = 4
    - 注意:重点!!!
      - 1.除了在   xhr.onreadystatechange 指定的回调函数的 readyState = 4 时取值
      - 2.还可以在 xhr.onload事件中取值
          xhr.onload = function() {
            // 请求成功
            if (xhr.status === 200 ) {
              // do successCallback
            }
          }
      - 3.判断 xhr.status === 200 是有坑的,因为成功时返回的状态码不只有200,下面的写法更靠谱
          xhr.onload = function () {
            //如果请求成功
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
              // 304 not modified 资源未被修改 协商缓存
              // 2开头的状态码,表示请求成功
              //do successCallback
            }
          }
    
    (8) xhr.timeout
    - 设置过期时间
    - 问题1:请求的开始时间怎么确定?是 ( xhr.onloadstart ) 事件触发的时候,也就是xhr.send()调用的时候
    - 解析:因为xhr.open()只是创建了链接,当并没有真正传输数据,只有调用xhr.send()时才真正开始传输
    - 问题2:什么时候是请求结束?
    - 解析:( xhr.loadend ) 事件触发时结束
    
    (9)  xhr.onprogress 下载进度信息
    (10) xhr.upload.onprogress 上传进度信息
    xhr.upload.onprogress = function(e) {
        if ( e.lengthComputable ) {
          const present = e.loaded / e.total * 100;
        }
    }
    
    • XMLHttpRequest请求案例
    XMLHttpRequest请求案例
    
    ----
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <button id="buttonId">点击,请求数据</button>
      <script>
        const button = document.getElementById('buttonId')
        button.addEventListener('click', handleClick, false)
    
        function handleClick() {
          const xhr = new XMLHttpRequest()
          xhr.open('GET', 'http://image.baidu.com/channel/listjson?pn=0&rn=30&tag1=明星&tag2=全部&ie=utf8', true) // open()方法
          xhr.setRequestHeader('Content-Type', 'application/json') // setRequestHeader必须在open()方法后,send()方法前调用,即 readyState === 1时
          xhr.responseType = 'text'
          xhr.timeout = 10000
          xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
              // 这里通过 this 代替 xhr 其实是一样的
              // 因为 this 在运行时确定指向,xhr实例在调用onreadystatechange方法,所以this指向xhr实例
              console.log(JSON.parse(this.responseText)) // 等价于 console.log(JSON.parse(xhr.responseText))
            }
          }
          xhr.onload = function () {
            if ((xhr.status >= 200 && xhr.status < 300) || (xhr.status === 304)) {
              console.log(JSON.parse(xhr.responseText), 'xhr.onload是在请求完成时触发的回调')
            }
          }
          xhr.send() // 发送请求
        }
      </script>
    </body>
    </html>
    
    image

    axios

    1. package.json => main: index.js => 说明入口文件是 index.js
    2. index.js => module.exports = require('./lib/axios');

    (1) axios请求流程梳理

    • createInstance 函数签名:(config) => axios instance

    • var axios = createInstance(defaults),进入createInstance函数内部

    • <font color=red size="5">var context = new Axios(defaultConfig)</font>

    • <font color=red>Axios</font>

      • function Axios(instanceConfig){...} 是一个构造函数
      • 实例
        • defaults
          • this.defaults = instanceConfig
        • interceptors 对象
          • request对象
            • new InterceptorManager() 生成的实例对象
          • response对象
            • new InterceptorManager() 生成的实例对象
      • Axios.prototype
        • request
        • getUri
        • 'delete', 'get', 'head', 'options', 'post', 'put', 'patch' 等请求的方法
    • <font color=red>InterceptorManager</font>

      • InterceptorManager 是一个构造函数
        • InterceptorManager 构造函数 生成的实例上有 ( 一个属性 )
          • handlers
            • 是一个数组
            • 数组的成员是这样的对象 {fulfilled: fulfilled, rejected: rejected} 或者 null
        • InterceptorManager.prototype 上有 ( 三个方法 )
          • use
          • eject
          • forEach
    • <font color=red>InterceptorManager.prototype.use - 添加拦截器</font>
      • 函数签名:(fulfilled, rejected) => (this.handlers.length - 1)
      • 参数
        • fulfilled
        • rejected
      • 返回值
        • (this.handlers.length - 1)
        • 表示该对象在this.handers数组中的下标,主要用于InterceptorManager.prototype.eject方法
      • 主要作用:
          1. 向 handlers 中 push {fulfilled: fulfilled, rejected: rejected} 对象
          1. 返回一个 (this.handlers.length - 1) 作为删除该对象的ID,其实就是该成员在数组中的下标
    • <font color=red>InterceptorManager.prototype.eject - 删除拦截器</font>
      • 参数
        • id
        • InterceptorManager.prototype.eject(id)
      • 主要作用:
        • 删除这个id对应的在 handlers 数组中的该位置上的对象
        • 注意:这里不是真正的删除,而是将这个id对应的属性设置成null,在forEach中遍历时会判断筛选过滤掉null项
    • <font color=red>InterceptorManager.prototype.forEach - 过滤掉null成员,遍历执行 fn(h) </font>
      • 参数
        • fn
        • InterceptorManager.prototype.forEach(fn)
      • 主要作用:
        • 遍历 this.handlers 数组,将数组中值不等于null的每个对象作为 fn(h) 的参数并执行
      • 实现:
        • 主要通过utils.forEach来实现
    • <font color=red>forEach - utils/forEach</font>
      • utils.js中的forEach方法
      • forEach(obj, fn)
      • 主要作用
        • 如果参数obj是null和undefined直接返回,不在往下执行
        • 如果参数obj是number,string,boolean,function则转成数组再进行后面的判断
        • 如果参数obj是数组( 即number,string,boolean,function.array都会进行数组的遍历 ),就循环遍历数组
          • fn.call(null, obj[i], i, obj) i 是数组下标
        • 如果参数obj是对象
          • fn.call(null, obj[key], key, obj); key 是对象的key
    • <font color=blue>mergeConfig</font>
      • 函数签名:(config1, config2) => config
      • 参数:
        • config1
        • config2
      • 返回
        • config
        • 即合并之后的总的config对象
    • <font color=blue>isObject - utils/isObject</font>
      • val !== null && typeof val === 'object'
      • 如果不是null并且typeof是一个对象就返回true
    • <font color=blue>isArray - utils/isArray</font>
      • Object.prototype.toString.call(val) === '[object Array]'
    • <font color=blue>merge - utils/merge</font>
      • 函数签名:(obj1, obj2, ...) => obj
      • 参数:对象或者数组,最好是纯对象
      • 返回值:一个合并过后的对象
      • 原理
          1. 循环遍历每个参数对象的每一个属性
          1. 如果result结果对象这个key对应的value是对象或者数组,并且参数对象这个key对应的value也是对象或者数组,就递归执行merge,从新合并这两个对象
          1. 如果result结果对象这个key对应的value不是对象或者数组,就直接以参数对象的key为key,value为value赋值给result对象
          • 这样下一个对象也存在相同属性,则result对象的该属性重新被赋值为后面参数独享的值,即会被重新赋值相当于覆盖
    • <font color=blue>deepMerge - utils/deepMerge</font>
      • 和merge的区别
        • 当参数对象2中的一个属性是对象,而参数对象1中没有该属性时候,仍然递归调用deepMerge,而不是直接赋值
        • 结果就是:
          • deepMerge返回的对象,当参数对象这个属性被修改时,不影响deepMerge返回的对象
          • merge返回的对象,当参数对象这个属性被修改时,merge返回的对象的该属性也会跟着改变
          • 即 浅拷贝和深拷贝的区别
    • <font color=blue>transformData</font>
      • 函数签名:(data, headers, fns) =>
    • <font color=blue>getDefaultAdapter</font>
      • 返回值:adapter
      • 主要作用
        • 根据不同的环境做不同的请求
        • 浏览器:XMLHttpRequest请求
        • node:http https模块
      • 区分浏览器还是node环境
        • 浏览器环境
          • XMLHttpRequest 存在
          • 存在,加载 adapter = require('./adapters/xhr')
        • node环境
          • process存在
          • 存在,加载 adapter = require('./adapters/http')
    • <font color=blue size=5>Axios.prototype.get - 以get方法举例</font>
      • 函数签名:(url, data) => this.request(utils.merge(config||{}, {method: method, url: url}))
      • this.request就是调用时所在的对象上的request => Axios的实例上的request => this.request === Axios.prototype.request
    • <font color=blue size=5>Axios.prototype.request</font>
      • Axios.prototype.request(config)
      • (1) 如果config是一个字符串,那么config = arguments[1] || {};config.url = arguments[0];否则 config = config || {};
        • 字符串
          • config = arguments[1] || {};
          • config.url = arguments[0]
          • axios('example/url'[, config]) 这样满足config是一个string,则config就是第二个参数,第一个参数就是url
        • 对象
          • config = config || {}
      • (2) 合并config
        • config = mergeConfig(this.defaults, config);
      • (3) 设置config.method
        • 如果config.method存在,就转成小写
        • 如果config.method不存在,但是his.defaults.method存在,config.method = this.defaults.method.toLowerCase()
        • 以上两个都不存在,默认config.method = get
      • (4) var chain = [dispatchRequest, undefined];
        • 注意这里没有执行dispatchRequest函数
      • (5) <font color=blue>dispatchRequest</font>
        • 函数签名:(config) => adapter(config).then(value => value, reason => Promise.reject(reason))
        • 返回值:请求完成后的,一个新的promise
      • (6) 根据45:var chain = [(config) => adapter(config).then(value => value, reason => Promise.reject(reason)), undefined]
      • (7) 拦截器先略过
      • (8) <font color=blue>var promise = Promise.resolve(config)</font>
        • 将config对象转成promise对象,该对象是resolve状态,其值会作为then(value => {})方法的第一个成功回调的参数即value的值
      • (9) this.interceptors.request.forEach(interceptor => { chain.unshift(interceptor.fulfilled, interceptor.rejected) })
        • 循环遍历request中的this.handlers数组,将每一个项作为interceptor参数,unshift到chain中
        • <font color=blue>chain = [ 请求拦截器的成功方法,请求拦截器的失败方法,dispatchRequest, undefined]</font>
        • 这里要特别注意:
          • request: new InterceptorManager()
          • response: new InterceptorManager()
          • <font color=blue>this.handlers在构造函数中,而不是在构造函数的prototype上,即 handles 数组在每个实例上,相互独立</font>
            • <font color=blue>axiosInstance.interceptors.request.use 和 axiosInstance.interceptors.response.use</font>
            • <font color=blue>两个use是向不同的独立的 handlers 中 push 和 unshift 对象</font>
            • <font color=blue>request是unsfift在前面</font>
            • <font color=blue>response是push在后面</font>
      • (10) this.interceptors.response.forEach(interceptor => {chain.push(interceptor.fulfilled, interceptor.rejected)})
        • 和9同理
        • 循环遍历response中的handlers数组,将每一项作为入这里的forEach回调函数的参数
        • <font color=blue>chain = [ 请求拦截器的成功方法,请求拦截器的失败方法,dispatchRequest, undefined, 响应拦截器的成功方法,响应拦截器的失败方法 ]</font>
      • (11) while 循环chain数组,执行 <font color=blue>promise = promise.then(chain.shift(), chain.shift())</font>
        • promise是传入请求的合并后的最终的配置对象
        • 每次从chain数组中从前往后取出两个元素
        • <font color=blue>var chain = [请求拦截器的成功方法,请求拦截器的失败方法, (config) => adapter(config).then(value => value, reason => Promise.reject(reason)), undefined, 响应拦截器的成功方法,响应拦截器的失败方法]</font>
        • 注意:这里的config就是(8)步骤中的config被转换成的promise对象,该对象就会作为 .then 方法成功回调的参数即config
        • 得到:
    
    var chain = [请求拦截器的成功方法,请求拦截器的失败方法, (config) => adapter(config).then(value => value, reason => Promise.reject(reason)), undefined, 响应拦截器的成功方法,响应拦截器的失败方法]
    
    promise = Promise.resolve(config);
        .then('请求成功拦截2', '请求失败拦截2') // 依此向下传递config , 注意2在1前面,unshift
        .then('请求成功拦截1', '请求失败拦截1')
        .then(dispatchRequest, undefined) // 真正发送请求,(config) => adapter(config).then(value => value, reason => Promise.reject(reason))
        .then('响应成功拦截1', '响应失败拦截1')
        .then('响应成功拦截2', '响应失败拦截2') // 注意2在1后面,push
    
        - (12) 最后返回 promise 
            - then()返回一个新的promise实例
            - 就又可以通过 .then() 方法获取请求得到的结果值 
    

    (2) axios请求流程,拦截原理 - 源码

    1. package.json => main => index.js => equire('./lib/axios')

    1. package.json => main => index.js => equire('./lib/axios')
    
    'use strict';
    
    var utils = require('./utils');·
    var bind = require('./helpers/bind');
    var Axios = require('./core/Axios');
    var mergeConfig = require('./core/mergeConfig');
    var defaults = require('./defaults');
    
    function createInstance(defaultConfig) {
      // createInstance(defaultConfig)
      // 参数:配置对象
      // 作用:创建一个axios实例,并返回
    
      var context = new Axios(defaultConfig);
      // 生成axios实例context
      // 实例属性
        // 实例属性:defaults = instanceConfig = 传入的参数对象defaultConfig
        // 实例属性: interceptors 对象
        // interceptors.request = new InterceptorManager()
        // interceptors.response = new InterceptorManager()
          // 实例属性:handlers 一个数组
          // 原型对象上的属性:use eject forEach
            // use    => 向 handlers 添加 {fulfilled: fulfilled, rejected: rejected}对象,返回数组下标,下标用于eject
            // eject  => 向 handlers设置该下标成员为null,相当于删除,因为在forEach中会判断是否为null
            // forEach => 循环handlers数组,依此执行 fn(h); 
              // fn  => 是forEach传入的参数函数
              // h   => 是handlers中的每个成员,对象4
      // 原型对象上的属性 
        // request 
        // getUri
        // 'delete', 'get', 'head', 'options', 'post', 'put', 'patch' 等请求的方法
      // 总结:
        // (1) context对象上有 defaults interceptors request getUri 'delete', 'get', 'head', 'options', 'post', 'put', 'patch' 等请求的方法
        // (2) interceptors.request 和  interceptors.response 上有 handlers use eject forEach
    
      var instance = bind(Axios.prototype.request, context);
      // bind 方法类似于原声的bind
      // 这里bind就是返回一个函数 warp() => Axios.prototype.request.apply(context, [...arguments])
      // 所以 instance 是一个函数,这个函数将request中的this绑定在context上并返回执行结果
      // var instance = warp() => Axios.prototype.request.apply(context, [...arguments])
    
    
      utils.extend(instance, Axios.prototype, context);
      // 继承原型
      // utils.extend作用:
      // 将 Axios.prototype 所有属性和方法都赋值给 instance
      // 如果是 Axios.prototype 上的方法,还要将this绑定到context
      // 最终: instance方法上具有 Axios.prototype 上的所有属性和方法
    
    
      utils.extend(instance, context);
      // 继承实例
      // 将 context 上的所有属性和方法赋值给  instance
    
      return instance;
    }
    
    
    var axios = createInstance(defaults);
    // 创建axios实例
    
    
    axios.Axios = Axios;
    // Expose Axios class to allow class inheritance
    // 可以通过 new axios.Axios()
    
    
    axios.create = function create(instanceConfig) {
      return createInstance(mergeConfig(axios.defaults, instanceConfig));
    };
    // Factory for creating new instances
    // 总结:创建axios实例的三种方法
    // 1. axios(configObject)
      // axios 本身就是这样一个函数 warp() => Axios.prototype.request.apply(context, [...arguments])
    // 2. axios.create(configObject)
      // 调用createInstance(mergeConfig(axios.defaults, instanceConfig))
    // 3. new axios.Axios().request(configObject)
    
    
    
    axios.Cancel = require('./cancel/Cancel');
    // Cancel(message) 是一个构造函数
    // 实例
      // message
    // 原型
      // toString
      // _CANCEL = true
    axios.CancelToken = require('./cancel/CancelToken');
    // CancelToken(executor) 是一个构造函数
      // 实例
        // promise
        // reason
      // 原型
        // throwIfRequested
      // 静态方法
        // source
          // 返回 {token: token, cancel: cancel} 这样一个对象
    // new CancelToken(executor)
      // 开发中这样调用: cancelToken = new axios.CancelToken(function executor(c) { cancel = c}) !!!!!!!!!!!!!!!!!!!!!!!!!!
      // 1. 如果 executor 不是函数,抛错
      // 2. this.promise = 一个promise对象,一直出于pending状态,在调用resolve(message)时改变状态
      // 3. executor函数的参数也是一个函数,即 c 函数
      // 4. c 函数主要作用就是 resolve(new Cancel(message)) 当执行后,
      // 5. 在Axios.prototype.request => dispatchRequest => defaults => getDefaultAdapter
        // throwIfCancellationRequested(config)
          // 如果config.cancelToken存在, config.cancelToken.throwIfRequested() 即 throw this.reason 即 throw new Cancel(message) 即一个 {message: 'xxxx'}
      // 5. 在Axios.prototype.request => dispatchRequest => defaults => getDefaultAdapter => xhr.js 中就行XMLHttpRequest请求
        // 会判断 config.cancelToken 是否存在
        //  存在,用 abort() 中断执行,将resolve(message)的message作为then()回调的参数reject(message)
      // 6. 总结:
        // config.cancelToken = {
        //   promise: new Promise(function(resolve){
        //     resolve({ message: '...'})
        //   }),
        //   reason: { message: '....' }
        // }
    
    axios.isCancel = require('./cancel/isCancel');
    // 标志位,用来标记是否取消
    // 以上三个属性主要用来取消请求
    
    
    // Expose all/spread
    axios.all = function all(promises) {
      return Promise.all(promises);
    };
    axios.spread = require('./helpers/spread');
    
    module.exports = axios;
    
    
    module.exports.default = axios;
    // Allow use of default import syntax in TypeScript
    // 即可以通过 import axios from 'axios'方式引入
    

    2. lib/core/Axios.js ---------- Axios中有各种请求方法 和 Axios.prototype.request 方法

    3. 以axios.get('www.baidu.com', {....}) get请求为例

    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
        }));
      };
    });
    // 上面的代码表示:遍历数组,添加各种方法
    // 比如:Axios.prototype.get = function(url, config) { return request(....) }
    // 注意:this.request()是在调用时才能确定this的指向,不如通过 axios.get()这样的话 this 就是 axios实例
    
    
    
    -----
    解析:先看 utils.forEach 这个函数定义
    1. 参数:obj 和 fn
    2. 如果obj是null或者undefined直接返回中断执行
    3. 如果是 number,string,boolean,function,symbol就把 obj 构造成数组
    4. 如果 obj 是数组,即经过3处理过的数据类型都是数组 =================> fn.call(null, obj[i], i, obj) 
        // obj[i]是数组的值
        // i 是下标
        // obj 是原数组
    5. 如果 obj 是对象,=================================================> fn.call(null, obj[key], key, obj)
        // obj[key]是对象的属性对应的值
        // key 是对象的属性
        // obj 是原对象
    function forEach(obj, fn) {
      if (obj === null || typeof obj === 'undefined') {
        return;
      }
      if (typeof obj !== 'object') {
        /*eslint no-param-reassign:0*/
        obj = [obj];
      }
      if (isArray(obj)) {
        for (var i = 0, l = obj.length; i < l; i++) {
          fn.call(null, obj[i], i, obj);
        }
      } else {
        for (var key in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, key)) {
            fn.call(null, obj[key], key, obj);
          }
        }
      }
    }
    

    4. Axios.prototype.request - 请求的核心方法,每个方法都会调用该方法

    Axios.prototype.request = function request(config) {
      // axios('example/url'[, config]) 这种情况config就是一个string
      // axios 就是 warp() => Axios.prototype.request.apply(context, [...arguments]) 这样一个函数
      if (typeof config === 'string') {
        config = arguments[1] || {}; // 第二个参数是config对象
        config.url = arguments[0]; // 第一个参数是url
      } else {
        config = config || {};
      }
    
      config = mergeConfig(this.defaults, config); 
      // 根据不同条件合并传入的参数对象和默认的参数对象,返回合并后的config
      // 暂且不看mergeConfig具体的合并规则
    
    
      if (config.method) {
        config.method = config.method.toLowerCase();
      } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
      } else {
        config.method = 'get';
      }
      // 如果传入的config中有method,使用传入的method并转为小写
      // 如果不存在,但默认cofig中method存在,使用默认
      // 都不存在,get方法
      
    
    
      var chain = [dispatchRequest, undefined];
      // dispatchRequest --------------------------------------------------------------------------------------- 分析1
      // var chain = [(config) => adapter(config).then(), null]
      
      
      
      var promise = Promise.resolve(config);
      // 将传入的参数对象 config 转成 promise 对象
    
      this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
        // 向chain数组循环unshift
        // chain = [请求拦截器的成功方法2,请求拦截器的失败方法2, 请求拦截器的成功方法1,请求拦截器的失败方法1, (config) => adapter(config).then(), null]
      });
      // this.interceptors.request.forEach --------------------------------------------------------------------- 分析2
    
      this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        chain.push(interceptor.fulfilled, interceptor.rejected);
         // 向chain数组循环push
        // chain = [请求拦截器的成功方法2,请求拦截器的失败方法2, 请求拦截器的成功方法1,请求拦截器的失败方法1, (config) => adapter(config).then(), null, 响应拦截器的成功方法1,响应拦截器的失败方法1, 响应拦截器的成功方法2,响应拦截器的失败方法2]
      });
    
      while (chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
        // 从左往右一次一次取出两个方法
        // promise = Promise.resolve(config);
          // .then('请求成功拦截2', '请求失败拦截2') // 依此向下传递config , 注意2在1前面,unshift
          // .then('请求成功拦截1', '请求失败拦截1')
          // .then(dispatchRequest, undefined) // 真正发送请求,(config) => adapter(config).then(value => value, reason => Promise.reject(reason))
          // .then('响应成功拦截1', '响应失败拦截1')
          // .then('响应成功拦截2', '响应失败拦截2') // 注意2在1后面,push
      }
    
      return promise;
      // 最后返回 promise 
      // then()返回一个新的promise实例
      // 就又可以通过 .then() 方法获取请求得到的结果值 
    };
    
    分析1 
    dispatchRequest
    函数签名:(config) => adapter(config).then()
    
    
    function throwIfCancellationRequested(config) {
      if (config.cancelToken) {
        config.cancelToken.throwIfRequested();
      }
    }
    
    module.exports = function dispatchRequest(config) {
      throwIfCancellationRequested(config);
      // 如果config中cancelToken存在,就表示取消请求,throw this.reason = throw new Cancel(message) = throw {messsage: '...'}
    
      config.headers = config.headers || {};
    
      config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
      );
    
      config.headers = utils.merge( 
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
      );
      // tuils.merge(obj1, obj2)
        // 循环遍历所有参数对象,并遍历每个参数对象的每个属性
        // 把每个属性和值赋值给新的对象
        // 如果新对象该属性和参数对象的该属性都是一个对象,递归
        // 如果新对象该属性不存在或者不是对象,直接赋值
        
        // 注意区分utils.deepMerge 和 utils.merge
        // deepMerge => 当新对象中属性不存在,参数对象该属性是一个对象时,不直接赋值,而是浅拷贝
    
      utils.forEach(
        ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
        function cleanHeaderConfig(method) {
          delete config.headers[method];
        }
      );
    
      var adapter = config.adapter || defaults.adapter;
      // adapter会根据浏览器环境或node环境使用不同的请求
      // 浏览器 => XMLHttpRequest来判断 => XHR
      // node => process来判断 => http https
    
      return adapter(config).then(function onAdapterResolution(response) {
        // 返回请求获取的数据,promise包装
        
        throwIfCancellationRequested(config);
        // 如果config.cancelToken存在,终止响应,抛出错误
    
        // Transform response data
        response.data = transformData(
          response.data,
          response.headers,
          config.transformResponse
        );
    
        return response; // 返回请求结果包装处理后的对象
      }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
          throwIfCancellationRequested(config);
    
          // Transform response data
          if (reason && reason.response) {
            reason.response.data = transformData(
              reason.response.data,
              reason.response.headers,
              config.transformResponse
            );
          }
        }
    
        return Promise.reject(reason);
      });
    };
    
    
    分析2
    InterceptorManager.prototype.forEach 
    参数:fn
    作用:循环handlers数组,将每个成员作为毁掉函数的参数,即执行 fn(h)
    注意:
    1. this.handlers是 [{fulfilled: fulfilled,rejected: rejected}] 这样的数组 或者成员是 null
    2. axios.interceptors.requet.handlers 和 axios.interceptors.requet.handlers 是不同的数组,因为是两次执行了 new InterceptorManager() 是不同的实例
        - request: new InterceptorManager()
        - response: new InterceptorManager
    
    
    InterceptorManager.prototype.forEach = function forEach(fn) {
      utils.forEach(this.handlers, function forEachHandler(h) {
        if (h !== null) {
          fn(h);
        }
      });
    };
    
    

    (3) axios取消请求 - 源码

    • 以这种取消请求的使用方法为例
    const CancelToken = axios.CancelToken;
    const source = CancelToken.source(); // ----------------------------- source工厂函数
    
    axios.get('/user/12345', {
      cancelToken: source.token // --------------------------------------- token
    }).catch(function (thrown) {
      if (axios.isCancel(thrown)) {
        console.log('Request canceled', thrown.message);
      } else {
        // handle error
      }
    });
    
    // 取消请求
    source.cancel('Operation canceled by the user.'); // ----------------- cancel函数
    

    1. lib/axios.js

    axios.Cancel = require('./cancel/Cancel');
    axios.CancelToken = require('./cancel/CancelToken');
    axios.isCancel = require('./cancel/isCancel'); 
    

    2. axios.CancelToken = require('./cancel/CancelToken')

    • CancelToken(executor)
    • CancelToken 是一个构造函数
      • 实例
        • promise
        • reason
    • 注意:<font color=blue>生成的cancelToken实例是要挂载到请求方法的config对象中的</font>
      • 像这样 axios.get('/user/12345', { cancelToken: source.token }).catch()
    'use strict';
    
    var Cancel = require('./Cancel');
    
    
    function CancelToken(executor) {
      if (typeof executor !== 'function') {
        // executor必须是一个函数
        throw new TypeError('executor must be a function.');
      }
    
      var resolvePromise;
      this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve;
        // this.promise 是一个处于pending状态的promise对象
      });
    
      var token = this; // 固定this
    
      executor(function cancel(message) {
      // 这里是调用 executor,调用时又传入了一个cancel函数作为 executor的参数
      // executor 是传入 CancelToken 的参数函数
    
      // executor的 ( 参数 ) 也是一个 ( 函数 ),即 cancel 函数
      // 这里是 cancel 函数定义
      // cancel函数的参数是 message
    
        if (token.reason) {
          return;
        }
    
        token.reason = new Cancel(message);
        // this.resaon不存在,就 new Cancel(message)
        // new Cancel(message) 是一个构造函数
          // 实例
            // messge
          // 原型
            // toString
            // __CANCEL__ = true
        // 所以
          // reason 就是一个 {message: 'xxxx'} 的对象
    
        resolvePromise(token.reason);
        // 改变 this.promise 的状态, 由pending 变为 resolve
      });
    }
    // CancelToken 总结
      // CancelToken构造函数生成的实例属性有两个 promise 和 reason
      // token = {
      //   promise: new Promise(resolve => resolve({message: 'xxxxxxx'})),
      //   reason: {messge: 'xxxxxx'}
      // }
      
      //  注意:生成的cancelToken实例是要挂载到请求的config对象中的 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        // `axios.get('/user/12345', { cancelToken: source.token }).catch()`像这样 !!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
    
    CancelToken.prototype.throwIfRequested = function throwIfRequested() {
      // reason存在,就抛错
      if (this.reason) {
        throw this.reason;
      }
    };
    
    
    CancelToken.source = function source() {
      // 静态方法 source
      var cancel;
      var token = new CancelToken(function executor(c) {
        // 这里的c就是上面 executor函数的参数 cancel函数
        // c函数主要是:把this.promise的状态由pending变为resolve
        cancel = c;
      });
      return {
        token: token, // token是{promise: new Promise(resolve => resolve({messge: ''})), reason: {message: ''}}这样的对象
        cancel: cancel // 取消函数
      };
    };
    
    module.exports = CancelToken;
    
    

    (3) 当cancel()函数执行时,请求传入的config对象中的cancelToken属性对象中的promise状态变为resolve之后,就进入到 dispatchRequest

    • <font color=blue>在各个阶段都需要判断 config 中 cancelToken 是否存在,存在抛错,中断执行</font>
    • 请求前
    • 请求时
    • 响应后等
    module.exports = function dispatchRequest(config) {
      throwIfCancellationRequested(config);
      // 在发送之前,就判断config中是否存在cancelToken
      // 如果存在就抛错,中断执行
    
      config.headers = config.headers || {};
    
      // Transform request data
      config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
      );
    
      // Flatten headers
      config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
      );
    
      utils.forEach(
        ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
        function cleanHeaderConfig(method) {
          delete config.headers[method];
        }
      );
    
      var adapter = config.adapter || defaults.adapter;
    
      return adapter(config).then(function onAdapterResolution(response) {
        // 在 adapter(config) 中 => default.js中 => adapter文件夹中的 => 浏览器环境 xhr.js 中
        // 做了如下判断
            // if (config.cancelToken) {
            //   config.cancelToken.promise.then(function onCanceled(cancel) {
            //     if (!request) {
            //       return;
            //     }
            //     request.abort();
            //     reject(cancel);
            //     request = null;
            //   });
            // }
            // 注意:上面的config.cancelToken.promise.then()的promise状态的改变是在调用了 cancel 函数时,变成fulfilled状态
            // 也就是说:当cancel函数执行时,请求才会去 abort 中断请求,并抛错
        
        throwIfCancellationRequested(config);
        // 求请完成,得到相应数据时,也判断config中是否存在cancelToken
        // 如果存在就抛错,中断执行
    
    
        // Transform response data
        response.data = transformData(
          response.data,
          response.headers,
          config.transformResponse
        );
    
        return response;
      }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
          throwIfCancellationRequested(config);
          // 出错时也判断config中是否存在cancelToken
        // 如果存在就抛错,中断执行
    
    
          // Transform response data
          if (reason && reason.response) {
            reason.response.data = transformData(
              reason.response.data,
              reason.response.headers,
              config.transformResponse
            );
          }
        }
    
        return Promise.reject(reason);
      });
    };
    

    (4) 在 dispatchRequest => adapter => xhr 中

    if (config.cancelToken) {
      // 如果cancelToken存在
      config.cancelToken.promise.then(function onCanceled(cancel) {
        // 当cancel()函数执行时 => config.cancelToken.promise状态已经是resolve({message: message})
        // 即执行 cancel 函数时,进行了 resolve 操作
        if (!request) {
          return;
        }
        request.abort(); // 中断请求
        reject(cancel); // 抛出错误,message
        // Clean up request
        request = null;
      });
    }
    

    实例运用:axios中的取消请求CancelToken在vue项目中的运用

    • 需求1
      • 如果每个页面只有一个请求,那么可以通过 mixin 把 token和cancel注入到每个组件的data中
    • 需求2
      • 每次路由跳转,都取消掉上个路由的所有请求
        1. 路由跳转时,利用路由守卫 beforeEach,执行store中的所有 cancel 取消函数
        1. 在axios的请求拦截中
        • new axios.CancelToken(c => cancel = c)把实例赋值给config.cancelToken属性;
        • 将 cancel 函数 push 到 store 中存储

    项目源码 - 源码分析地址

    资料

    川神 https://juejin.im/post/6844904019987529735
    重点cancelToken https://juejin.im/post/6844903910650413070
    https://juejin.im/post/6844903910650413070#heading-0
    axios官网 https://github.com/axios/axios
    vue和react中如何优雅地使用axios取消请求 https://juejin.im/post/6844904023191977998

    相关文章

      网友评论

          本文标题:[源码] axios

          本文链接:https://www.haomeiwen.com/subject/sopngltx.html