xhr.abort(), 300);取消请求用xhr.abort(),那么什么时候执行这个取消请求的操作呢?得有一_axios.isca">
当前位置:   article > 正文

axios(十)-- 取消请求_axios.iscancel

axios.iscancel

Axios 中的公共方法

1、关键点(思路)

如果要取消请求的话,可以通过调用 XMLHttpRequest 对象上的 abort 方法来取消请求:
let xhr = new XMLHttpRequest();
xhr.open("GET", "https://developer.mozilla.org/", true);
xhr.send();
setTimeout(() => xhr.abort(), 300);
  • 1
  • 2
  • 3
  • 4
取消请求用xhr.abort(),那么什么时候执行这个取消请求的操作呢?

得有一个信号告诉 xhr 对象什么时候执行取消操作。
取消请求就是未来某个时候要做的事情,那就想到是 Promise。
Promise 的 then 方法只有 Promise 对象的状态变为 resolved 的时候才会执行。
利用这个特点,在 Promise 对象的 then 方法中执行取消请求的操作。

2、使用

而对于 Axios 来说,我们可以通过 Axios 内部提供的 CancelToken 来取消请求:
两种方法

一:
const cancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
    cancelToken: source.token
}).catch(function(thrown) {
    if (axios.isCancel(thrown)) {	// 源码有,判断是否取消请求
        console.log('Request canceled', thrown.message);
    } else {
        // 处理错误
    }
});

axios.post('/user/12345', {
    name: 'new name'
}, {
    cancelToken: source.token
})

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
二:还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// cancel the request
cancel();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3、源码

axios.js中引入
let defaults = require('./defaults')
let utils = require('./utils')

function createInstance(defaultConfig) {
    // 创建 axios 实例
    var context = new Axios(defaultConfig);
  
    // 把 instance 指向 Axios.prototype.request 方法
    // bind返回一个新的函数,内部去执行request
    var instance = bind(Axios.prototype.request, context);
  
    // extend 将第二个参数对象的方法拷贝到第一个对象上去
    // 把 Axios.prototype(原型对象) 上的方法扩展到 instance 上,指定上下文是 context 
    // 方法有:request()/get()/post()/put()/delete() 
    utils.extend(instance, Axios.prototype, context);
  
    // 把 context 上的方法扩展到 instance 上
    // 把 Axios实例对象 上的方法扩展到 instance 上,有defaults 和 interceptors属性
    utils.extend(instance, context);
  
    // 导出 instance 对象
    return instance;
  }


  // 实例一个 axios
  var axios = createInstance(defaults);
  
  // 添加 create 方法,返回 createInstance 函数,参数为自定义配置 + 默认配置
  axios.create = function create(instanceConfig) {
    return createInstance(mergeConfig(axios.defaults, instanceConfig));
  };
  
    //   ...
  // 向这个实例添加 CancelToken 方法
    axios.Cancel = require('./cancel/Cancel');
    axios.CancelToken = require('./cancel/CancelToken');
    axios.isCancel = require('./cancel/isCancel');
    
    // Expose all/spread
    axios.all = function all(promises) {
      return Promise.all(promises);
    };

// 导出实例 axios
  module.exports = axios;
  
  // Allow use of default import syntax in TypeScript
  module.exports.default = axios;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
CancelToken.js

CancelToken 挂载 source 方法用于创建自身实例,并且返回 {token, cancel}
token 是构造函数 CancelToken 的实例,cancel 方法接收构造函数 CancelToken 内部的一个 cancel 函数,用于取消请求
创建实例中,有一步是创建处于 pengding 状态的 promise,并挂在实例方法上,外部通过参数 cancelToken 将实例传递进 axios 内部,内部调用 cancelToken.promise.then 等待状态改变
当外部调用方法 cancel 取消请求,pendding 状态就变为 resolve,即取消请求并且抛出 reject(message)

export class CancelToken {
    // new的时候执行构造函数
    constructor(exactor) {
        if(typeof exactor !== 'function') {
            throw new TypeError('executor must be a function.')
        }
        // 将promise的决定权交给了cancel函数
        // 同时做了防止多次cancel, 
        // promise里将resolve存到外部,即外部也可以调用
        const resolvePromise;
        this.promise = new promise(resolve => {
            resolvePromise = resolve
        })
        this.reason = undefined

        const cancel = (message) => {
            // 如果有reason说明已经取消过了,直接return
            if (this.reason) {
                // 取消过的直接返回
                return
            }
            // 外部调用 cancel 取消请求方法,Cancel 实例化,保存 message 并增加已取消请求标示
            //  new Cancel(message) 后等于 { message,  __CANCEL__ : true}
            this.reason = new Cancel(message)
            // 将取消请求的promise指定为成功,值为reason
            resolvePromise(this.reason)
        }
        // 立即执行接受的执行器函数,并传入用于取消请求的cancel函数
        exactor(cancel)
    }
    
    // source 其实本质上是一个语法糖 里面做了封装
    static source () {
         /**
         * 构造函数 CancelToken 实例化,用回调函数做参数,并且回调函数
         * 接收 CancelToken 内部的函数 c,保存在变量 cancel 中,
         * 后面调用 cancel 即取消请求
         */
        const cancel;
        const token = new CancelToken(function executor(c) {
            cancel = c
        })
        return {
            cancel: cancel,
            token: token
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
Cancel.js
'use strict';
/**
 * 当取消一个请求时, 需要将Cancel对象作为一个error抛出
 * A `Cancel` is an object that is thrown when an operation is canceled.
 *
 * @class
 * @param {string=} message The message.
 */
function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

// 用于标识是一个取消的error
Cancel.prototype.__CANCEL__ = true;
module.exports = Cancel;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
isCancel.js
'use strict';
/* 
用于判断一个error是不是一个cancel错误
*/
module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__);
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
xhr.js

当我们执行 const source = CancelToken.source()的时候,source 对象有两个字段,
一个是 token 对象,另一个是 cancel 函数。

module.exports = function xhrAdapter (config) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

         // 设置完整路径,拼接url,例如:https://www.baidu,com + /api/test
        var fullPath = buildFullPath(config.baseURL, config.url);
        // 初始化一个请求,拼接url,例如:https://www.baidu,com/api/test + ?a=10&b=20
        xhr.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
        
        // 超时断开,默认 0 永不超时
        xhr.timeout = config.timeout;
        
        xhr.onreadystatechange = function() {
          // request不存在或请求状态不是4, 直接结束
          if (!request || request.readyState !== 4) {
            return;
          }
    
          // The request errored out and we didn't get a response, this will be
          // handled by onerror instead
          // With one exception: request that using file: protocol, most browsers
          // will return status as 0 even though it's a successful request
          if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
            return;
          }
    
          // 准备response对象
          var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
          var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
          var response = {
            data: responseData,
            status: request.status,
            statusText: request.statusText,
            headers: responseHeaders,
            config: config,
            request: request
        }
        // 根据响应状态码来确定请求的promise的结果状态(成功/失败)
        settle(resolve, reject, response);

        // 将请求对象赋空
        request = null;
      };

       // 绑定请求中断监听
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }
      // reject promise, 指定aborted的error
      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Clean up request
      request = null;
    };

    // Handle low level network errors
    request.onerror = function handleError() {
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    };

    // Handle timeout
    request.ontimeout = function handleTimeout() {
      reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };
        
        
        // 标准浏览器(有 window 和 document 对象)
        if (utils.isStandardBrowserEnv()) {
          // 非同源请求,需要设置 withCredentials = true,才会带上 cookie
          var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
            cookies.read(config.xsrfCookieName) :
            undefined;
          if (xsrfValue) {
            requestHeaders[config.xsrfHeaderName] = xsrfValue;
          }
        }
        // request对象携带 headers 去请求
        if ('setRequestHeader' in xhr) {
          utils.forEach(requestHeaders, function setRequestHeader(val, key) {
            if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
              // data 为 undefined 时,移除 content-type,即不是 post/put/patch 等请求
              delete requestHeaders[key];
            } else {
              xhr.setRequestHeader(key, val);
            }
          });
        }

        // 如果有配置config.cancelToken
        if(config.cancelToken) {
            // 处理取消的  config.cancelToken.promise. --> CancelToken.js里
            config.cancelToken.promise.then(function onCanceled(cancel) {
                if (!xhr) {
                    return
                }
                // 中断请求
                xhr.abort()
                // 让请求的promise失败
                reject(cancel)
                // clean up xhr,请求结束要把xhr置为null, 不为null说明请求还没有完成
                xhr = null
            })
        }

        // 发送请求
        xhr.send(requestData)
    })
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119

这里只做了get处理,主要的目的就是为了axios是如何取消请求的
CancelToken 的构造函数中需要传入一个函数,
而这个函数的作用其实是为了将能控制内部 Promise 的 resolve 函数暴露出去,暴露给 source 的 cancel 函数。
这样内部的 Promise 状态就可以通过 source.cancel() 方法来控制啦

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/652675
推荐阅读
相关标签
  

闽ICP备14008679号