从源码入手学会Axios取消请求的使用

160 阅读2分钟

序言

我们都知道axios里面有个取消请求的功能,但是看他文档的用例会不理解为什么要这样写,也不知道具体作用是什么,今天就来探讨一下他的执行过程,知道内部怎么实现的就不怕不会用了

用法

先来cv一下文档的用例,这里简单说一下执行过程,如果在配置里面添加了cancelToken属性就会把它里面的取消请求的方法push_listeners队列里面,然后触发source.cancel方法就会循环队列全部中断

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.');

简单分析源码执行过程

从入口开始axios有这样一段代码,暴露了CancelToken方法

// lib/axios.js

axios.CancelToken = require('./cancel/CancelToken') //返回的是CancelToken的构造函数

我们来看看内部结构:(看中文注释就好啦)

  • 第一步 通过调用source返回一个对象,该对象包含一个新的'CancelToken'和一个实例化后CancelToken函数里面的方法
  • 第二步 new 实例通过回调函数把 cancel方法返回出去
  • 第三步 看配置里面有没有config.cancelToken
  • 第四步 执行source.cancel()触发回调,最终使用的是request.abort()中断请求
// lib/adapters/xhr.js
// ...省略代码

 // 如果配置里有 cancelToken 进行取消请求
    if (config.cancelToken || config.signal) {
      // Handle cancellation
      // eslint-disable-next-line func-names
      onCanceled = function(cancel) {
        // cancel = token.reason
        if (!request) {
          return;
        }
        reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
        request.abort();
        request = null;
      };

      config.cancelToken && config.cancelToken.subscribe(onCanceled);
      if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }

如果有的话执行config.cancelToken.subscribe把取消方法添加到_listeners数组里面

// ./cancel/CancelToken.js
'use strict';

var Cancel = require('./Cancel');

/**
 * CancelToken是一个可以用来请求取消操作的对象
 *
 * @class
 * @param {Function} executor The executor function.
 */
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;

  this.promise = new Promise(function promiseExecutor(resolve) {
    // resolve暴露出去
    resolvePromise = resolve;
  });

  var token = this;

  // eslint-disable-next-line func-names
  // 第四步执行这段代码
  this.promise.then(function(cancel) {
    // cancel = token.reason

    if (!token._listeners) return;
    var i;
    var l = token._listeners.length;

    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });

  // eslint-disable-next-line func-names
  // 这个可以不管
  this.promise.then = function(onfulfilled) {
    var _resolve;
    // eslint-disable-next-line func-names
    var promise = new Promise(function(resolve) {
      token.subscribe(resolve);
      _resolve = resolve;
    }).then(onfulfilled);

    promise.cancel = function reject() {
      token.unsubscribe(_resolve);
    };

    return promise;
  };

  /**
 * 第二步
 * new 实例通过回调函数把 cancel方法返回出去
 * c=cancel方法
 */
  executor(
    // 第四步 执行回调
    function cancel(message) {
      if (token.reason) {
        // 已请求取消
        return;
      }

      /**
    * 设置取消提示 内部返回这些属性的对象
    *  token.reason = {
    *  message: '',
    *  toString() { },
    *  __CANCEL__: true
    *  }
    */
      token.reason = new Cancel(message);

      // resolvePromise(token.reason)==> resolve(token.reason)
      resolvePromise(token.reason);
    });
}

/**
 * Throws a `Cancel` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

/**
 * Subscribe to the cancel signal
 * 第三步执行这段代码
 */
CancelToken.prototype.subscribe = function subscribe(listener) {
  if (this.reason) {
    listener(this.reason);
    return;
  }

  if (this._listeners) {
    this._listeners.push(listener);
  } else {
    this._listeners = [listener];
  }
};

/**
 * Unsubscribe from the cancel signal
 */

CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
  if (!this._listeners) {
    return;
  }
  var index = this._listeners.indexOf(listener);
  if (index !== -1) {
    this._listeners.splice(index, 1);
  }
};

/**
 * 第一步
 * 返回一个对象,该对象包含一个新的'CancelToken'和一个函数,
 * 当调用时取消'CancelToken'
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    // c= function cancel(message)
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;