axios 取消请求方法及原理探索

349 阅读2分钟

前言

以前刚入行时,个人单打独斗为这个问题探索了好久,还写了一篇文章记载过程,想想仿佛还在昨天。没办法现在我们要卷,不能只知其然,不知其所以然。在验证的过程中也加深了自己背的八股文的理解。验证地址

复习下axios 取消请求两种方式

  1. 一定要看文档、看文档、看文档。
  2. 我开始在 serve 里面用 koa 使接口返回 sleep(2000);然后想着 2s 够我点取消按钮了吧。实验才发现请求是瞬发的,除非我自己网络有问题发不出去。所以就换条路子,浏览器肯定有并发限制的。直接冲个100条看看。我就不信你不进队列等待。果然成功了。可以想象以前理解的是多么肤浅。
utils/axios.js
import axios from 'axios';
const CancelToken = axios.CancelToken;
export const cancelTokenMap = new Map();
axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 取消方法一
  config.cancelToken = new CancelToken(function executor(c) {
    cancelTokenMap.set(config.url, c.bind(null, config.url));
  })
  // 取消方法二
  // const source = CancelToken.source(); 
  // config.cancelToken = source.token;
  // cancelTokenMap.set(config.url, source.cancel);
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

App.vue
const cancelAll = () => {
    // 请欣赏封面图那令人沉醉的中国红
    Array(100).fill(1).forEach((el,i) => {
      const firstCancel = cancelTokenMap.get(`/sendFirst?queryKey=${i}`);
      firstCancel && firstCancel();
    });
}
const sendAjax = () => {
    Array(100).fill(1).forEach((el,i) => {
      sendFirst(i).then(() => {
        // console.log('sendFirst--', res)
      });
    })
}

知其然知其所以然 cancel 流程

  1. 用户设置 config.cancelToken = new CancelToken(function executor(cancel){});
  2. xhr.js xhrAdapter() 里 config.cancelToken.subscribe(onCanceled) 订阅取消事件
  3. cancelToken.js CancelToken.prototype.subscribe 使用 this._listeners。存储订阅的事件;
  4. new CancelToken(functionon executor(cancel){}) 用户执行 cancel 函数;
  5. resolvePromise(token.reason)就会执行,触发 this.promise.then
  6. this.promise.then 里面 this._listeners 执行,那么 onCanceled就会执行 -> request.abort(); 自此整个串起来了,到这里就会把请求取消,不会执行到下面 request.send(requestData);
  7. 至此完成了 request 的取消。
入口文件 axios.js
axios.js
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.VERSION = require('./env/data').version;
cancel/CancelToken.js
// 不长 50行代码
function CancelToken(executor) {
  var resolvePromise;
  var token = this;
  this.promise = new Promise(function promiseExecutor(resolve) {
      resolvePromise = resolve;
  });
  this.promise.then(function(cancel) {
    var i;
    var l = token._listeners.length;
    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}
adapters/xhr.js
// 先只看客户端部分,node 是放在 http.js 一样。axios 使用适配器来统一两端的实现.
function xhrAdapter(config) {
    new Promise(function dispatchXhrRequest(resolve, reject) {
        if (config.cancelToken || config.signal) {
          onCanceled = function(cancel) {
            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);
          }
          // Send the request
          request.send(requestData);
        }
    })
}

我们学到什么

  1. import axios from 'axios' 项目全局都是同一个对象,

axios.js: axios = new Axios(config); export default axios;

  1. 适配器方法 抹平客户端 xhr.js 与 node 端 http.js 的差异 实现方法统一;
  2. 发布订阅模式 cancelToken.js _listeners/subscribe
  3. cancelToken.js this.promise 的巧妙利用,把控制权交给用户。
  4. cancelToken.js source是构造函数方法也是基于 cancelToken 实例来实现提供更多选择。
  5. 存下封面图片链接