Axios 异步网络请求-小结

174 阅读5分钟

写在前面,准备

安装 node新版本

安装 json-server

以下内容是学习 尚硅谷Web前端axios入门与源码解析 总结的。

1. 认识 axios

截屏2021-07-10 10.06.17.png

2. 为 axios应用准备案例

截屏2021-07-10 10.20.34.png

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>axios基本使用</title>
    <link crossorigin="anonymous" href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
</head>
<body>
    <div class="container">
        <h2 class="page-header">基本使用</h2>
        <button class="btn btn-primary"> 发送GET请求 </button>
        <button class="btn btn-warning" > 发送POST请求 </button>
        <button class="btn btn-success"> 发送 PUT 请求 </button>
        <button class="btn btn-danger"> 发送 DELETE 请求 </button>
    </div>
    <script>
        //获取按钮
        const btns = document.querySelectorAll('button');

        //第一个
        btns[0].onclick = function(){
            //发送 AJAX 请求
            axios({
                //请求类型
                method: 'GET',
                //URL
                url: 'http://localhost:3000/posts/2',
            }).then(response => {
                console.log(response);
            });
        }

        //添加一篇新的文章
        btns[1].onclick = function(){
            //发送 AJAX 请求
            axios({
                //请求类型
                method: 'POST',
                //URL
                url: 'http://localhost:3000/posts',
                //设置请求体
                data: {
                    title: "今天天气不错, 还挺风和日丽的",
                    author: "张三"
                }
            }).then(response => {
                console.log(response);
            });
        }

        //更新数据
        btns[2].onclick = function(){
            //发送 AJAX 请求
            axios({
                //请求类型
                method: 'PUT',
                //URL
                url: 'http://localhost:3000/posts/3',
                //设置请求体
                data: {
                    title: "今天天气不错, 还挺风和日丽的",
                    author: "李四"
                }
            }).then(response => {
                console.log(response);
            });
        }

        //删除数据
        btns[3].onclick = function(){
            //发送 AJAX 请求
            axios({
                //请求类型
                method: 'delete',
                //URL
                url: 'http://localhost:3000/posts/3',
            }).then(response => {
                console.log(response);
            });
        }

    </script>
</body>

</html>

3. ES6中 Promise的原理和应用

截屏2021-07-11 08.25.28.png

4 创建实例对象

//创建实例对象  /getJoke
        const duanzi = axios.create({
            baseURL: 'https://api.apiopen.top',
            timeout: 2000// 请求超时时间2s
            // headers 可以通过在这里设置,也可以在request拦截器里创建
        });

        const onather = axios.create({
            baseURL: 'https://b.com',
            timeout: 2000
        });
        //这里  duanzi 与 axios 对象的功能几近是一样的
        // duanzi({
        //     url: '/getJoke',
        // }).then(response => {
        //     console.log(response);
        // });

        duanzi.get('/getJoke').then(response => {
            console.log(response.data)
        })

5. axios的配置

请求配置

这些是创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 get 方法。

{
   // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // default

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data, headers) {
    // 对 data 进行任意转换处理
    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理
    return data;
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },

   // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,

   // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default

  // `adapter` 允许自定义处理请求,以使测试更轻松
  // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },

 // `auth` 表示应该使用 HTTP 基础验证,并提供凭据
  // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
  auth: {
    username: 'july',
    password: 'jjjjjuly'
  },

   // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // default

  // `responseEncoding` indicates encoding to use for decoding responses
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // default

   // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default

   // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

   // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,

  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // default
  },

  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // default

  // `socketPath` defines a UNIX Socket to be used in node.js.
  // e.g. '/var/run/docker.sock' to send requests to the docker daemon.
  // Only either `socketPath` or `proxy` can be specified.
  // If both are specified, `socketPath` is used.
  socketPath: null, // default

  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启用
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // 'proxy' 定义代理服务器的主机名称和端口
  // `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
  // 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'aaaaaaaa',
      password: 'bbbbbbb'
    }
  },

  // `cancelToken` 指定用于取消请求的 cancel token
  // (查看后面的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
}

响应结构

某个请求的响应包含以下信息

{   // `data` 由服务器提供的响应  
data: {},   
// `status` 来自服务器响应的 HTTP 状态码   
status: 200,    
// `statusText` 来自服务器响应的 HTTP 状态信息  
statusText: 'OK',   
// `headers` 服务器响应的头  
headers: {},     
// `config` 是为请求提供的配置信息 
config: {},  
// 'request'   
// `request` is the request that generated this response   // It is the last ClientRequest instance in node.js (in redirects)  
// and an XMLHttpRequest instance the browser  
request: {} 
} 

使用 then 时,你将接收下面这样的响应 :

axios.get('/user/12345')  
.then(function(response) {  
console.log(response.data);  
console.log(response.status);   
console.log(response.statusText);  
console.log(response.headers);     
console.log(response.config);   
}); 

在使用 catch 时,或传递 rejection callback 作为 then 的第二个参数时,响应可以通过 error 对象可被使用。

默认配置

        axios.defaults.method = 'GET';//设置默认的请求类型为 GET
        axios.defaults.baseURL = 'http://localhost:3000';//设置基础 URL
        axios.defaults.params = {id:100};
        axios.defaults.timeout = 3000

6. axios的拦截器应用(interceptors)

image.png

如果,请求拦截器 throw '参数出问题了',那么响应拦截器会返回失败的Promise,后续,只能走失败的回调。

// 设置请求拦截器
        axios.interceptors.request.use(function (config) {
            console.log('请求拦截器 成功')
            // return config;
            throw '参数出问题了'
        }

image.png

请求拦截器后进先执行,响应拦截器先进先执行 image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>拦截器</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
</head>
<body>
    <script>
        // promise 相关
        // 设置请求拦截器
        axios.interceptors.request.use(function (config) {
            console.log('请求拦截器 成功 -1号'),
            // 修改config配置中的参数
            config.params = {a:100}

            return config;
        }, function (error) {
            console.log('请求拦截器 失败 -1号');
            return Promise.reject(error);
        });

        axios.interceptors.request.use(function (config) {
            console.log('请求拦截器 成功 -2号')
            // 修改config配置中的参数
            config.timeout = 5000 
            // return config;
        }, function (error) {
            console.log('请求拦截器 失败 -2号');
            return Promise.reject(error);
        });

        // 设置响应拦截器
        axios.interceptors.response.use(function (response) {
            console.log('响应拦截器 成功  -1号');            
            return response.data;
        }, 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('自定义回调处理成功的结果');
            console.log(response)
        }).catch(reason => {
            console.log('自定义失败的回调')
        })

    </script>   
</body>
</html>

7. 请求取消

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>取消请求</title>
    <link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
</head>
<body>
    <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")

        // 声明全局变量
        let cancel = null;

        // 发送请求
        btns[0].onclick = function(){
            // 检测上一次的请求是否已经完成
            if(cancel !== null){
                // 取消上一次请求
                cancel()
            }
            axios({
                method:'GET',
                url:'http://localhost:3000/posts',

                // 1.添加配置对象的属性
                cancelToken: new axios.CancelToken(function(f){
                    // 将 f的值赋给cancel
                    cancel = f
                })
            }).then(response =>{
                console.log(response)
                // 将cancel的值初始化设置
                cancel = null
            })
        } 

        // 绑定第二个按钮,取消请求
        btns[1].onclick = function(){
        
            cancel()
        }
    </script>   
</body>
</html>

8 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>
    <!-- <script src="./node_modules/axios/dist/mine-axios.js"></script> -->
</head>
<body>
    <script>
        // console.log(axios);

        //构造函数
        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'});
        }

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

            })

            //为instance 函数对象添加属性, default 与 interceptor
            Object.keys(context).forEach(key =>{
                instance[key] = context[key]
            })
            return instance;
        }

        let axios = createInstance()
        //发送请求
        axios.get({})
        axios.post({})


    </script>
</body>
</html>

Axios 发送请求过程

原理是:

axiso的request 请求 --> dispatch --> xhr 的信息,

然后信息由xhr --> dispatch -->request 路径传回,最终返回到

response =>{
    console.log(response)
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>请求发送过程</title>
    <!-- <script src="./node_modules/axios/dist/mine-axios.js"></script> -->
</head>
<body>
    <script>
        // axios 发送请求   axios 发送请求的源头是在 Axios.prototype.request bind
        // 0 声明构造函数

        function Axios(config) {
            this.config = config;
        }

        Axios.prototype.request = function(config){
            //1 发送请求
            // 其实,在1.1之前合并了config ,这些操作就暂不展示
            // 1.1创建一个 promise对象
            let promise = Promise.resolve(config)
            console.log(promise)
            //声明一个数字
            let chains = [dispatchRequest,undefined]//undefined 作用就是占位,上原型链的知识
            // 调用then方法 指定回调
            let result =  promise.then(chains[0],chains[1])//由于前面的Promise是成功的回调,所以这里调用的一定是chains[0],也就是dispatchRequest
            // let result =  promise.then(console.log('dispatchRequest函数'),chains[1]) //这里与上面是一个意思

            // 返回 promise 的结果
            return result//这里result是返回给Axios.prototype.request的执行结果,(这里的request与下面的axios函数是等效的),也就是返回给下面的axios函数,再由axios.then方法去执行,就拿到结果,
        }


        //2 dispatchRequest 函数,  
        /* 这里,如果AJAX发送请求成功,则进行正确的回调,返回response的结果,进一步的, let result =  promise.then(chains[0],chains[1])中的结果就是chains[0],,也就是dispatchRequest,也就是response成功的结果值
        如果失败,则进行失败的回调,throw error
        */
        function dispatchRequest(config) {
            //2.1调用适配器发送请求
            return xhrAdapter(config).then(response => {
                console.log(response)//相当于 console.log('xhrAdapter 函数执行')
                // 2.2 对响应的结果进行转换处理
                return response
            },Error =>{
                throw error
            })
        }
        // 3 adapter适配器
        function xhrAdapter(config) {
            console.log('xhrAdapter 函数执行')
            return new Promise((resolve,reject)=>{
                // 发送ajax请求
                let 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){
                                //成功状态
                                config: config//配置对象
                                data: xhr.response//响应体
                                header: xhr.getAllResponseHeaders()//响应头;字符串,对头信息进行解析
                                request: xhr//xhr请求对象
                                status: xhr.status//响应状态码
                                statusText: xhr.statusText//响应状态字符串
                            }else{
                                // 失败的状态
                                reject(new Error('请求失败 失败的状态码为'  + xhr.status))
                            }
                        }
                    }
                    
            })
        }

        // 4 创建axios函数 
        let axios = Axios.prototype.request.bind(null)
        
        axios({
            method:'GET',
            url:'http://localhost:3000/posts'
        }).then(response => {
            console.log(response);
        });
    </script>
</body>
</html>

axios拦截器功能模拟实现

axios拦截器 返回结果的顺序为

1639550425(1).png

原理: 由于Axios.js中 会依次取出 chain 的回调函数,并执行,如下图,chain的长度是10,shift方法是向前添加,会打乱顺序。

axios.interceptors.request.use中.use 方法只是将回调的结果保存在request的handers属性中。

在执行时,对函数进行分组,以跳板的形式,一组一组的,将请求拦截器放前面,响应拦截器放后面。

当都是成功的回调时,跳板形式(红线路径): image.png

当有失败的回调时,跳板形式(蓝线路径): 如果是undefined,那么穿透,继续下一个回调。 image.png

image.png

拦截器实现原理:

  • 通过拦截器管理工具保存回调,

  • request 负责整合,先讲发送请求的dispatchRequest和undefined 放一起,undefined起到重要作用,作为正常跳转的保障,然后将请求拦截器往前放,将响应拦截器往后放,最后整合为一个数组。

  • 接着遍历数组,通过promise中链条的方式去执行里面回调。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>拦截器</title>
    <!-- <script src="./node_modules/axios/dist/mine-axios.js"></script> -->
</head>
<body>
    <script>
        //构造函数
        function Axios(config) {
            this.config = config;
            this.interceptors = {
                request:new InterceptorManager(),
                response:new InterceptorManager()
            }
          }

        // 发送请求 ☆ 重点部分
        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)
            })

            // console.log(chains)
            /*此时,数组就发生了变化:
            0: ƒ two(config)
            1: ƒ two(error)
            2: ƒ one(config)
            3: ƒ one(error)
            4: ƒ dispatchRequest(config)
            5: undefined
            length: 6

            */
            // 响应拦截器
            this.interceptors.response.handlers.forEach(item => {
                chains.push(item.fulfilled,item.rejected)
            })
            // console.log(chains)
             /*此时,数组又就发生了变化:
            0: ƒ two(config)
            1: ƒ two(error)
            2: ƒ one(config)
            3: ƒ one(error)
            4: ƒ dispatchRequest(config)
            5: undefined
            6: ƒ (response)
            7: ƒ (error)
            8: ƒ (response)
            9: ƒ (error)
            length: 10
            */

            // // 到这里数组准备完毕,下面开始遍历
            while(chains.length > 0){
                promise = promise.then(chains.shift(),chains.shift())//先拿回请求拦截器2号,
            }
            return promise

        } 
        //发送请求
        function dispatchRequest(config) { 
            // 返回promise对象
            return new Promise((resolve,reject)=>{
                resolve({
                    status:200,
                    statusText:'OK'
                })
            })
        }

        // 创建实例
        let context = new Axios({})
       // 创建axios函数
       let axios = Axios.prototype.request.bind(context)
        // 将context 属性,包括 config 、interceptors 添加到 axios函数对象上
        Object.keys(context).forEach(key =>{
            axios[key] = context[key]
        }) 
        // console.dir(axios)


        //   拦截器管理器 构造函数
        function InterceptorManager() {
            this.handlers = [];
        }
        InterceptorManager.prototype.use = function (fulfilled,rejected) {
            this.handlers.push({
                fulfilled,
                rejected
            })
        }

 
        //1 以下为功能测试代码
        // 1.1设置请求拦截器  config 配置对象
        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);
        });

        // 1.2 设置响应拦截器
        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);
        });


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

Axios 取消请求工作 模拟实现

Axios 取消请求原理:

就是在 cancelToken 上维护了一个promise属性,然后将改变promise的 resolvePromise 变量暴露到全局,也就是用一个变量cancel 去改变它的值,只有执行cancel(),内部的resolvePromise 就执行,

于是 resolve 执行,

接着,this.promise 状态就变成功,然后,

 config.cancelToken.promise.then(value =>{
                        xhr.abort()
                    } )

中的回调就执行,就执行力xhr.abort(),因此,请求取消。

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

            })
        }

cancelToken构造函数 中的 executor 就是 CancelToken中的

function(c){
                // 3 将 c 赋值给 cancel
                cancel = c;
            }

,红线标注的。

image.png

另外,function(c) 中的c 是 虚拟参数 ,实参是executor 函数中的function 。(蓝线标注的)

image.png

现在开始取消请求操作

 if(config.cancelToken){
                    // 对 cancelToken 对象中 的 promise对象 指定成功的回调,也就是
                    /*
                    new CancelToken(function(c){ cancel = c; });
                    */
                    config.cancelToken.promise.then(value =>{
                        xhr.abort()
                    } )
                   
                }

image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>取消请求</title>
    <link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- <script src="./node_modules/axios/dist/mine-axios.js"></script> -->
</head>
<body>
    <div class="container">
        <h2 class="page-header">axios取消请求</h2>
        <button class="btn btn-primary"> 发送请求 </button>
        <button class="btn btn-warning"> 取消请求 </button>
    </div>
    <script>
        //构造函数
        function Axios(config){
            this.config = config;
        }
        //原型 request 方法
        Axios.prototype.request = function(config){
            return dispatchRequest(config);
        }
        //dispatchRequest 函数
        function dispatchRequest(config){
            return xhrAdapter(config);
        }
        //xhrAdapter
        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('请求失败'));
                        }
                    }
                }
                //关于取消请求的处理
                if(config.cancelToken){
                    //对 cancelToken 对象身上的 promise 对象指定成功的回调
                    config.cancelToken.promise.then(value => {
                        xhr.abort();
                        //将整体结果设置为失败
                        reject(new Error('请求已经被取消'))
                    });
                }
            })
        }

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

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

        //获取按钮 以上为模拟实现的代码
        const btns = document.querySelectorAll('button');
        //2.声明全局变量
        let cancel = null;
        //发送请求
        btns[0].onclick = 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].onclick = function(){
            cancel();
        }
    </script>   
</body>
</html>