从零实现axios(5.1小节-取消请求1)

389 阅读2分钟

通过AbortController取消请求

从v0.22.0版本开始,axios通过AbortController来取消请求,之前通过CancelToken来取消请求的方式已经被废弃,因此,我们只需要学习如何通过AbortController来取消请求即可。

AbortController的原理就是通过new AbortController()创建一个controller控制器对象,然后通过controller.signal监听abort事件,要取消请求时,执行controller.abort()即可。

// 创建一个abort controller
const controller = new AbortController();

// 监听abort信号,取消请求时会触发onCanceled回调
controller.signal.addEventListener('abort', onCanceled);

// 取消请求,会触发onCanceled回调
controller.abort()

实际使用axios取消请求,就是通过在配置中把controller.signal传递进去来实现的,下面是具体用法的例子:

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// 取消请求
controller.abort()

我们在adapters/xhr.js文件来实现取消请求的具体逻辑。

var settle = require("../core/settle");
var parseHeaders = require("../helpers/parseHeaders");
var AxiosError = require('../core/AxiosError');
var CanceledError = require('../cancel/CanceledError');

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    //...
    var onCanceled;
    function done() {
      if (config.signal) {
        // 请求完成后,要取消监听abort事件
        config.signal.removeEventListener('abort', onCanceled);
      }
    }

    //...

    // 取消请求的回调函数,调用 request.abort() 时触发
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));

      // 把request置为空
      request = null;
    };

    // 如果配置中包含了signal,则在这里监听、触发取消请求事件
    if (config.signal) {
      onCanceled = function(cancel) {
        if (!request) {
          return;
        }
        // 这里不需要考虑cancelToken触发的取消,直接抛出CanceledError
        reject(new CanceledError(null, config, req));
        request.abort(); // 取消请求
        request = null;
      };

      if (config.signal) {
        // 如果用户已经触发controller.abort,则直接调用onCanceled回调来取消请求
        // 否则监听abort事件,等待用户触发
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }

    // ...

  });
};

上面代码中我们用到了CanceledError类来创建一个取消错误对象,取消操作时抛出该对象。该类就是对AxiosError类的一个封装,只不过把错误码指定为AxiosError.ERR_CANCELED,这是一种函数柯里化的小技巧。可以减少传参数量。

我们在根目录下创建cancel目录,在该目录下创建CanceledError.js文件,在该文件中实现CanceledError类

'use strict';

var AxiosError = require('../core/AxiosError');
var utils = require('../utils');

/**
 * 取消操作时抛出的对象
 *
 * @class
 * @param {string=} message 消息
 * @param {Object=} config 配置
 * @param {Object=} request XMLHttpRequest实例
 */
function CanceledError(message, config, request) {
  AxiosError.call(this, message == null ? 'canceled' : message, AxiosError.ERR_CANCELED, config, request);
  this.name = 'CanceledError';
}

// 继承AxiosError原型
utils.inherits(CanceledError, AxiosError, {
  __CANCEL__: true
});

module.exports = CanceledError;

我们还需要一个isCancel辅助函数帮助我们判断一个对象是否是CanceledError实例,我们继续在该目录下创建isCancel.js文件,在该文件中实现辅助函数

module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__);
};

我们在根目录的axios.js中,对外提供这个辅助函数

axios.CanceledError = require('./cancel/CanceledError');
axios.isCancel = require('./cancel/isCancel');