Axios(v1.1.3)源码阅读

588 阅读12分钟

1.准备工作

git clone https://github.com/axios/axios.git
# 也可以使用我带注释的Axios源码
git clone https://github.com/crush2020/myAxios-1.1.3.git
cd axios
npm install
npm run start
# open [http://localhost:3000](http://localhost:3000)
# chrome F12 source 控制面板  webpack//   .  lib 目录下,根据情况自行断点调试

如果代码没有用es语法进行重构下面这一步可以省略

当运行start命令时会发现终端报错__dirname未定义(当前模块的目录名),这是因为最新的代码全部用EsModule规范去写了,就会不识别node默认的全局变量__dirname,这个时候就要进行修改,用 **CommonJS规范给的api去模拟这个变量,修改为如下项目就可以跑起来了。

第一种
import url from 'url';
import path from 'path';
const __filename = url.fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename);
或者第二种
import { dirname } from "node:path"
import { fileURLToPath } from "node:url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename);

let server;
function pipeFileToResponse(res, file, type) {
  if (type) {
    res.writeHead(200, {
      'Content-Type': type
    });
  }

  fs.createReadStream(path.join(__dirname, file)).pipe(res);
}

修改sandbox/server.js文件,让调试时可以看到源文件

  if (pathname === '/index.html') {
    pipeFileToResponse(res, './client.html');
  } else if (pathname === '/axios.js') {
    pipeFileToResponse(res, '../dist/axios.js', 'text/javascript');
  } else if (pathname === '/axios.js.map') 
    // 原来的代码,因为打包后的映射文件文件叫axios.js.map,判断这里也要改
    // pipeFileToResponse(res, '../dist/axios.map', 'text/javascript');
    pipeFileToResponse(res, '../dist/axios.js.map', 'text/javascript');
  } 

2.axios 结构是怎样的

打开 http://localhost:3000,在控制台打印出axios,估计很多人都没打印出来看过。

console.log({axios: axios});

层层点开来看,axios 的结构是怎样的,先有一个大概印象。

1666073178648.jpg

3.Axios源码

看源码第一步,先看package.json。一般都会申明 main 主入口文件。

// package.json
{
  "name": "axios",
  "version": "1.1.3",
  "description": "Promise based HTTP client for the browser and node.js",
  "main": "index.js",
  // ...
}

主入口文件

// index.js
module.exports = require('./lib/axios');

3.1 lib/axios.js主文件

axios.js文件 代码相对比较多。分为三部分展开叙述。

  1. 第一部分:引入一些工具函数utilsAxios构造函数、默认配置defaults等。
  2. 第二部分:是生成实例对象 axiosaxios.Axiosaxios.create等。
  3. 第三部分取消相关API,cancelToken,signal的实现,还有allspread、导出等实现。

3.1.1 第一部分

引入一些工具函数utilsAxios构造函数、默认配置defaults,一些方法等。这些方法我会在讲解对应的代码时,进行说明

// 第一部分:
// lib/axios
// 严格模式
'use strict';

// 引入 utils 对象,有很多工具方法。
import utils from './utils.js';
// 引入 bind 方法
import bind from './helpers/bind.js';
// 核心构造函数 Axios
import Axios from './core/Axios.js';
// 合并配置方法
import mergeConfig from './core/mergeConfig.js';
// 引入默认配置
import defaults from './defaults/index.js';

// 引入一些方法
import formDataToJSON from './helpers/formDataToJSON.js';
import CanceledError from './cancel/CanceledError.js';
import CancelToken from './cancel/CancelToken.js';
import isCancel from './cancel/isCancel.js';
import {VERSION} from './env/data.js';
import toFormData from './helpers/toFormData.js';
import AxiosError from './core/AxiosError.js';
import spread from './helpers/spread.js';
import isAxiosError from './helpers/isAxiosError.js';

bind()函数

'use strict';
// 返回一个新的wrap函数,产生一个闭包
// 所以函数被调用时的this始终为传入的this
export default function bind(fn, thisArg) {
  return function wrap() {
    return fn.apply(thisArg, arguments);
  };
}

forEach()函数

遍历数组和对象。设计模式称之为迭代器模式。很多源码都有类似这样的遍历函数。

/**
 * @param {Object|Array} 需要遍历的数据
 * @param {Function} 传入的函数
 *
 * @param {Boolean} [allOwnKeys = false] 是否遍历可迭代属性
 * @returns {void}
 */
function forEach(obj, fn, {allOwnKeys = false} = {}) {
  // Don't bother if no value provided
  // 无值返回
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  let i;
  let l;

  // Force an array if not already something iterable
  // 不是对象放在数组里
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over array values
    // 是数组便利整个数组,并使用call调用函数,并把value,index,arr传入fn函数
    for (i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    
    //getOwnPropertyNames方法返回一个由指定对象的所有自身属性的属性名
    //(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。

    // keys方法会返回一个由一个给定对象的自身可枚举属性组成的数组
    
    // 判断allOwnKeys,有就调用getOwnPropertyNames,没有就调用keys
    const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
    const len = keys.length;
    let key;
	// 遍历整个keys,并使用call调用函数,并把value,key,obj传入fn函数
    for (i = 0; i < len; i++) {
      key = keys[i];
      fn.call(null, obj[key], key, obj);
    }
  }
}

utils.extend()函数

/**
 * @param {Object} a 要扩展的对象
 * @param {Object} b 被复制的对象
 * @param {Object} thisArg 函数被调用的this
 *
 * @param {Boolean} [allOwnKeys]
 * @returns {Object} The resulting value of object a
 */
// 其实就是遍历参数 b 对象,复制到 a 对象上,如果是函数就是则调用 bind 处理。
const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
  forEach(b, (val, key) => {
    if (thisArg && isFunction(val)) {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  }, {allOwnKeys});
  return a;
}

3.1.2 第二部分

是生成实例对象 axiosaxios.Axiosaxios.create等。

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 *
 * @returns {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // 创建一个新的Axios实例
  const context = new Axios(defaultConfig);
  const instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  // 复制 Axios.prototype 到实例instance上。
  // 也就是为什么 有 axios.get 等别名方法,
  // 且调用的是 Axios.prototype.get 等别名方法。
  utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});

  // Copy context to instance
  // 复制 context 到 intance 实例
  // 也就是为什么默认配置 axios.defaults 和拦截器  axios.interceptors 可以使用的原因
  // 其实是new Axios().defaults 和 new Axios().interceptors
  utils.extend(instance, context, null, {allOwnKeys: true});

  // Factory for creating new instances
  // 工厂模式 创建新的实例 用户可以自定义一些参数
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

// Create the default instance to be exported
// 创建默认实例
const axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
// 暴露 Axios calss 允许 class 继承
axios.Axios = Axios;

3.1.3 第三部分

取消相关API实现(这个在后面详细介绍),还有allspread、公开Axios错误信息类,导出等实现。

// Expose Cancel & CancelToken
// 取消相关API实现
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;

// Expose AxiosError class
// 公开Axios错误信息类
axios.AxiosError = AxiosError;

// alias for CanceledError for backward compatibility
// 用于向后兼容的CanceledError的别名
axios.Cancel = axios.CanceledError;

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};

// 就是利用apply把数组形式的参数转为一个个参数传入
axios.spread = spread;

// Expose isAxiosError
// 公开isAxiosError类
axios.isAxiosError = isAxiosError;

// 数据转换
axios.formToJSON = thing => {
  return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
};

export default axios

3.2核心构造函数 Axios

class Axios {
  constructor(instanceConfig) {
    // 默认配置
    this.defaults = instanceConfig;
    // 拦截器对象
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }

  /**
   * Dispatch a request
   * 核心方法
   * @param {String|Object} configOrUrl The config specific for this request
   * (merged with this.defaults)
   * @param {?Object} config
   *
   * @returns {Promise} The Promise to be fulfilled
   */
  request(configOrUrl, config) {
    // 核心方法后面单独讲
    ...
  }
  
// 这是获取 Uri 的函数,处理为正常请求的url,并把编码的解码
  getUri(config) {
    config = mergeConfig(this.defaults, config);
    const fullPath = buildFullPath(config.baseURL, config.url);
    return buildURL(fullPath, config.params, config.paramsSerializer);
  }
}

// Provide aliases for supported request methods
// 提供一些请求方法的别名
// 遍历执行
// 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的还是 Axios.prototype.request 方法
// 这个也在上面的 axios 结构图上有所体现。
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method,
      url,
      data: (config || {}).data
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/

  function generateHTTPMethod(isForm) {
    return function httpMethod(url, data, config) {
      return this.request(mergeConfig(config || {}, {
        method,
        headers: isForm ? {
          'Content-Type': 'multipart/form-data'
        } : {},
        url,
        data
      }));
    };
  }

  Axios.prototype[method] = generateHTTPMethod();

  Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});

3.3拦截器构造函数 InterceptorManager

如何使用:

// Add a request interceptor
// 添加请求前拦截器
axios.interceptors.request.use(function (config) {
  return config;
}, function (error) {
  return Promise.reject(error);
},{ synchronous: true, runWhen: onGetCall }
);

// Add a response interceptor
// 添加请求后拦截器
axios.interceptors.response.use(function (response) {
  return response;
}, function (error) {
  return Promise.reject(error);
});

// 也可以在创建的实例上使用
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

//移除
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

源码实现

'use strict';

import utils from './../utils.js';

class InterceptorManager {
  constructor() {
    //handles 用于存储拦截器函数。
    this.handlers = [];
  }

  /**
   * Add a new interceptor to the stack
   *
   * @param {Function} fulfilled The function to handle `then` for a `Promise`
   * @param {Function} rejected The function to handle `reject` for a `Promise`
   *
   * @return {Number} An ID used to remove interceptor later
   */
// 传递三个函数作为参数,fulfilled成功的回调函数,rejected失败的回调函数
// options额外参数,synchronous使拦截器同步运行,只有当runWhen()的返回为false时,才会执行拦截器
// 返回数字 ID,用于移除拦截器。
  use(fulfilled, rejected, options) {
    this.handlers.push({
      fulfilled,
      rejected,
      synchronous: options ? options.synchronous : false,
      runWhen: options ? options.runWhen : null
    });
    return this.handlers.length - 1;
  }

  /**
   * Remove an interceptor from the stack
   *
   * @param {Number} id The ID that was returned by `use`
   *
   * @returns {Boolean} `true` if the interceptor was removed, `false` otherwise
   */
  // 根据传入的id移除拦截器,及清空handlers对应的数据
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null;
    }
  }

  /**
   * Clear all interceptors from the stack
   *
   * @returns {void}
   */
  // 清空整个拦截器
  clear() {
    if (this.handlers) {
      this.handlers = [];
    }
  }

  /**
   * Iterate over all the registered interceptors
   * This method is particularly useful for skipping over any
   * interceptors that may have become `null` calling `eject`.
   * @param {Function} fn The function to call for each interceptor
   * @returns {void}
   */
  // 遍历执行所有拦截器,传入fn
  forEach(fn) {
    utils.forEach(this.handlers, function forEachHandler(h) {
      if (h !== null) {
        fn(h);
      }
    });
  }
}

export default InterceptorManager;

3.4实例结合

上文叙述的调试时运行npm start 是用axios/sandbox/client.html路径的文件作为示例的,读者可以自行调试。以下是一段这个文件中的代码

axios(options)
  .then(function (res) {
    response.innerHTML = JSON.stringify(res.data, null, 2);
    error.innerHTML = "None";
  })
  .catch(function (res) {
    error.innerHTML = JSON.stringify(res.toJSON(), null, 2)
    console.error('Axios caught an error from request', res.toJSON());
    response.innerHTML = JSON.stringify(res.data, null, 2);
  });

3.4.1调用栈流程

如果不想一步步调试,有个偷巧的方法。
知道 axios 使用了XMLHttpRequest
可以在项目中搜索:new XMLHttpRequest
定位到文件 axios/lib/adapters/xhr.js
在这条语句 let request = new XMLHttpRequest();
chrome 浏览器中 打个断点调试下,再根据调用栈来细看具体函数等实现。

Call Stack

dispatchXhrRequest (xhr.js:65)
xhrAdapter (xhr.js:46)
dispatchRequest (dispatchRequest.js:46)
configOrUrl (Axios.js.140)
wrap (bind.js:5)
submit.onclick ((index):145)

简述下流程:

  1. Send Request 按钮点击 submit.onclick
  2. 调用 axios 函数实际上是调用 Axios.prototype.request 函数,而这个函数使用 bind 返回的一个名为wrap的函数。
  3. 处理请求传入的参数configOrUrl
  4. (有请求拦截器的情况下执行请求拦截器),中间会执行 dispatchRequest方法
  5. dispatchRequest 之后调用 adapter (xhrAdapter)
  6. 最后调用 Promise 中的函数dispatchXhrRequest,(有响应拦截器的情况下最后会再调用响应拦截器)
  7. 如果有取消逻辑将会在请求发送后执行

3.5Axios.prototype.request 请求核心方法

  /**
   * Dispatch a request
   *
   * @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
   * @param {?Object} config
   *
   * @returns {Promise} The Promise to be fulfilled
   */
  request(configOrUrl, config) {
    /*eslint no-param-reassign:0*/
    // Allow for axios('example/url'[, config]) a la fetch API
    //判断第一个参数是字符串,则设置 url,也就是支持axios('example/url', [, config]),也支持axios({})。
    if (typeof configOrUrl === 'string') {
      config = config || {};
      config.url = configOrUrl;
    } else {
      config = configOrUrl || {};
    }
    // .合并默认参数和用户传递的参数
    config = mergeConfig(this.defaults, config);

    const {transitional, paramsSerializer} = config;

    if (transitional !== undefined) {
      validator.assertOptions(transitional, {
        silentJSONParsing: validators.transitional(validators.boolean),
        forcedJSONParsing: validators.transitional(validators.boolean),
        clarifyTimeoutError: validators.transitional(validators.boolean)
      }, false);
    }

    if (paramsSerializer !== undefined) {
      validator.assertOptions(paramsSerializer, {
        encode: validators.function,
        serialize: validators.function
      }, true);
    }

    // Set config.method
    //设置请求的方法,默认是是get方法
    config.method = (config.method || this.defaults.method || 'get').toLowerCase();

    // Flatten headers
    //处理单个请求的自定义headers
    const defaultHeaders = config.headers && utils.merge(
      config.headers.common,
      config.headers[config.method]
    );

    defaultHeaders && utils.forEach(
      ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
      function cleanHeaderConfig(method) {
        delete config.headers[method];
      }
    );

    config.headers = new AxiosHeaders(config.headers, defaultHeaders);

    // filter out skipped interceptors
    // 过滤掉跳过的其拦截器

    // 存储过滤后的请求拦截器
    const requestInterceptorChain = [];
    // 是否异步执行构造器,初始值为true
    let synchronousRequestInterceptors = true;
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      // 当拦截器的options的runWhen为函数且返回的结果为falses时跳出循环
      if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
        return;
      }
      // 当拦截器的options的synchronous === true 时,才为同步执行,否则为异步
      synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
      // 把请求拦截器器插入到数组开头,这就是为什么后写的请求拦截器先执行的原因
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    // 存储响应拦截器,
    const responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise;
    let i = 0;
    let len;

    // 默认时或者synchronous === false时异步执行请求
    if (!synchronousRequestInterceptors) {
      // 创建队列
      const chain = [dispatchRequest.bind(this), undefined];
      // 把处理后的拦截器插入任务队列的对应位置
      chain.unshift.apply(chain, requestInterceptorChain);
      chain.push.apply(chain, responseInterceptorChain);
      len = chain.length;

      // 给下一个promise传递一个参数config
      promise = Promise.resolve(config);

      // 按顺序一次执行队列里的任务
      while (i < len) {
        promise = promise.then(chain[i++], chain[i++]);
      }

      return promise;
    }

    
    // 处理同步时的任务队列
    len = requestInterceptorChain.length;

    let newConfig = config;

    i = 0;
    // 没有使用promise封装,保证了任务队列依次执行
    while (i < len) {
      const onFulfilled = requestInterceptorChain[i++];
      const onRejected = requestInterceptorChain[i++];
      try {
        newConfig = onFulfilled(newConfig);
      } catch (error) {
        onRejected.call(this, error);
        break;
      }
    }

    // 请求拦截同步时,执行的请求,出错抛出异常
    try {
      promise = dispatchRequest.call(this, newConfig);
    } catch (error) {
      return Promise.reject(error);
    }

    // 处理响应时拦截
    i = 0;
    len = responseInterceptorChain.length;

    while (i < len) {
      promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
    }

    return promise;
  }

3.6dispatchRequest 最终派发请求

'use strict';

import transformData from './transformData.js';
import isCancel from '../cancel/isCancel.js';
import defaults from '../defaults/index.js';
import CanceledError from '../cancel/CanceledError.js';
import AxiosHeaders from '../core/AxiosHeaders.js';

/**
 * Throws a `CanceledError` if cancellation has been requested.
 *
 * @param {Object} config The config that is to be used for the request
 *
 * @returns {void}
 */
  // 抛出 错误原因,使`Promise`走向`rejected`
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    throw new CanceledError();
  }
}

/**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 *
 * @returns {Promise} The Promise to be fulfilled
 */
export default function dispatchRequest(config) {
  // 取消相关
  throwIfCancellationRequested(config);
// 确保 headers 存在
  config.headers = AxiosHeaders.from(config.headers);

  // Transform request data
  // 转换请求的数据
  config.data = transformData.call(
    config,
    config.transformRequest
  );

  // adapter 适配器 真正发送请求
  const adapter = config.adapter || defaults.adapter;

  return adapter(config).then(
    function onAdapterResolution(response) {
      throwIfCancellationRequested(config);

      // Transform response data
      // 转换响应的数据
      response.data = transformData.call(
        config,
        config.transformResponse,
        response
      );

      response.headers = AxiosHeaders.from(response.headers);

      return response;
    }, 
    function onAdapterRejection(reason) {
      // 取消相关
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config);

        // Transform response data
        // 转换响应的数据
        if (reason && reason.response) {
          reason.response.data = transformData.call(
            config,
            config.transformResponse,
            reason.response
          );
          reason.response.headers = AxiosHeaders.from(reason.response.headers);
        }
      }

      return Promise.reject(reason);
    }
  );
}

3.7adapter请求适配器

根据当前环境引入,如果是浏览器环境引入xhr,是node环境则引入http

/**
 * If the browser has an XMLHttpRequest object, use the XHR adapter, otherwise use the HTTP
 * adapter
 *
 * @returns {Function}
 */
function getDefaultAdapter() {
  let adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // 根据 XMLHttpRequest 判断
    // For browsers use XHR adapter
    adapter = adapters.getAdapter('xhr');
  } else if (typeof process !== 'undefined' && utils.kindOf(process) === 'process') {
    // For node use HTTP adapter
    // 根据 process 判断
    adapter = adapters.getAdapter('http');
  }
  return adapter;
}

3.8xhr发送请求

接下来就是我们熟悉的 XMLHttpRequest 对象。

主要提醒下:

onabort是请求取消事件,

onreadystatechange函数XMLHttpRequest.onreadystatechange 会在 XMLHttpRequestreadyState 属性发生改变时触发 readystatechange (en-US) 事件的时候被调用。

XMLHttpRequest.abort()如果请求已被发出,则立刻中止请求。

export default function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    let requestData = config.data;
    const requestHeaders = AxiosHeaders.from(config.headers).normalize();
    const responseType = config.responseType;

    if (utils.isFormData(requestData) && platform.isStandardBrowserEnv) {
      requestHeaders.setContentType(false); // Let the browser set it
    }
    // eslint-disable-next-line no-debugger
    // 创建一个XML
    let request = new XMLHttpRequest();

    // HTTP basic authentication
    // HTTP基本身份验证
    if (config.auth) {
      const username = config.auth.username || '';
      const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
      requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
    }

    const fullPath = buildFullPath(config.baseURL, config.url);

    // 初始化一个请求。
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    // 设置超时时间
    request.timeout = config.timeout;

    let onCanceled;
    // 清除请求
    function done() {
      if (config.cancelToken) {
        config.cancelToken.unsubscribe(onCanceled);
      }

      if (config.signal) {
        config.signal.removeEventListener('abort', onCanceled);
      }
    }

    function onloadend() {
      if (!request) {
        return;
      }
      // Prepare the response
      const responseHeaders = AxiosHeaders.from(
        'getAllResponseHeaders' in request && request.getAllResponseHeaders()
      );
      const responseData = !responseType || responseType === 'text' ||  responseType === 'json' ?
        request.responseText : request.response;
      const response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config,
        request
      };

      // 返回结果
      settle(function _resolve(value) {
        resolve(value);
        done();
      }, function _reject(err) {
        reject(err);
        done();
      }, response);

      // Clean up request
      request = null;
    }

    if ('onloadend' in request) {
      // Use onloadend if available
      // 使用已配置的onloadend
      request.onloadend = onloadend;
    } else {
      // Listen for ready state to emulate onloadend
      // 监听 readyState  改变
      request.onreadystatechange = function handleLoad() {
        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;
        }
        // readystate handler is calling before onerror or ontimeout handlers,
        // so we should call onloadend on the next 'tick'
        //成功时调用onloadend()
        setTimeout(onloadend);
      };
    }

    // Handle browser request cancellation (as opposed to a manual cancellation)
    // 取消
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, 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(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));

      // Clean up request
      request = null;
    };

    // Handle timeout
     // 超时
    request.ontimeout = function handleTimeout() {
      let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
      const transitional = config.transitional || transitionalDefaults;
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(new AxiosError(
        timeoutErrorMessage,
        transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
        config,
        request));

      // Clean up request
      request = null;
    };

    // Add xsrf header 添加xsrf头
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    if (platform.isStandardBrowserEnv) {
      // Add xsrf header
      const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
        && config.xsrfCookieName && cookies.read(config.xsrfCookieName);

      if (xsrfValue) {
        requestHeaders.set(config.xsrfHeaderName, xsrfValue);
      }
    }

    // Remove Content-Type if data is undefined
    requestData === undefined && requestHeaders.setContentType(null);

    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
        request.setRequestHeader(key, val);
      });
    }

    // Add withCredentials to request if needed
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

    // Add responseType to request if needed
    // 修改响应类型
    if (responseType && responseType !== 'json') {
      request.responseType = config.responseType;
    }

    // Handle progress if needed
    // 上传下载进度相关
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
    }
     // 取消请求相关
    if (config.cancelToken || config.signal) {
      // Handle cancellation
      // eslint-disable-next-line func-names
      onCanceled = cancel => {
        if (!request) {
          return;
        }
        reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
        request.abort();
        request = null;
      };
      // 如果config.cancelToken === true,执行后边的并返回结果,如果为 False,返回config.cancelToken
      config.cancelToken && config.cancelToken.subscribe(onCanceled);
      if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }

    const protocol = parseProtocol(fullPath);

    if (protocol && platform.protocols.indexOf(protocol) === -1) {
      reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
      return;
    }


    // Send the request
    // 发送请求。
    request.send(requestData || null);
  });
}

3.9 dispatchRequest 之 取消模块

使用方法

// 从v0.22.0开始,Axios支持AbortController以fetch API方式取消请求:
const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// cancel the request
controller.abort()


// 从v0.22.0开始,此API已被弃用,不应在新项目中使用,但是最新的代码不知道为啥还支持
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).then(function (res) {
    console.log('Request success', res.message);
  }).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});
source.cancel('Operation canceled by the user.');

CancelToken取消流程(比较绕建议自己多调试几次)

1.调用axios.CancelToken.source()创建对象{ token,canncel }

  // 创建一个source时,被调用
  static source() {
    let cancel;
    const token = new CancelToken(function executor(c) {
      // 这里的c就是executor函数的参数,cancel函数
      cancel = c;
    });
    return {
      token,
      cancel
    };
  }

2.axios.get(conflig)请求开始,由于conflig中含有cancelToken在这里,会在这里调用cancelToken.subscribe把取消函数onCanceled传入任务队列_listeners

     // 取消请求相关
    if (config.cancelToken || config.signal) {
      // Handle cancellation
      // eslint-disable-next-line func-names
    	// 取消实际被调用的函数
      onCanceled = cancel => {
        // 请求就结束时,取消无效
        if (!request) {
          return;
        }
        // 否则就reject出去
        reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
        // 调用xml的abort()取消方法
        request.abort();
        //把这次请求的xml置为空
        request = null;
      };
      // 如果config.cancelToken === true,执行后边的并返回结果
      // // cancelToken来自axios.CancelToken
      config.cancelToken && config.cancelToken.subscribe(onCanceled);
    }

cancelToken.subscribe()函数

  subscribe(listener) {
    // 判断是否已经取消过了
    if (this.reason) {
      listener(this.reason);
      return;
    }

    // 判断有无其他的取消任务
    if (this._listeners) {
      this._listeners.push(listener);
    } else {
      this._listeners = [listener];
    }
  }

3.请求发送中,执行下一步代码source.cancel()

axios.get('/user/12345', {
  cancelToken: source.token
}).then(function (res) {
    console.log('Request success', res.message);
  }).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});
source.cancel('Operation canceled by the user.');

4.执行source.cancel(),等与执行CancelToken.executor()

    // 真正取消source.cancel()所调用的函数
    executor(function cancel(message, config, request) {
      // 是否已经取消
      if (token.reason) {
        // Cancellation has already been requested
        return;
      }

      // 设置取消信息
      token.reason = new CanceledError(message, config, request);
      // resolvePromise为this.promise成功的回调函数reslove
      //resolvePromise被调用,这个时候 this.promise.then()的reslove就可以被调用了
      resolvePromise(token.reason);
    });

5.调用resolvePromise(token.reason)传入错误信息对象;等于调用了this.promise成功的回调函数reslove

// 存储promise的成功的resolve回调函数,方便调用,取消时调用
    let resolvePromise;

    this.promise = new Promise(function promiseExecutor(resolve) {
      resolvePromise = resolve;
    });

6.this.promise.then()开始执行

// 这里的cancel就是resolvePromise(token.reason)中的token.reason
this.promise.then(cancel => {
  // 这里的取消任务队列_listeners就是subscribe()被执行的时候赋值的onCanceled函数
  // 判断有取消任务
  if (!token._listeners) return;

  let i = token._listeners.length;
  // 有就把任务拿出来依次执行,并传入错误信息对象token.reason
  while (i-- > 0) {
    token._listeners[i](cancel);
  }
  // 执行完后,取消任务队列赋值为空
  token._listeners = null;
});

7.执行取消任务队列_listeners中的listeners函数,_listeners为执行cancelToken.subscribe(onCanceled)传入的

  onCanceled = cancel => {
    // 请求就结束时,取消无效
    if (!request) {
      return;
    }
    // 否则就reject出去
    reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
    // 调用xml的abort()取消方法
    request.abort();
    //把这次请求的xml置为空
    request = null;
  };

8.取消结束

AbortController取消流程

1.创建AbortController对象传入signal属性

只读属性 signal 返回一个 AbortSignal 实例对象,该对象可以根据需要处理 DOM 请求通信,既可以建立通信,也可以终止通信。

// AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。
const controller = new AbortController();
options.signal = controller.signal

2.axios.get(conflig)请求开始,由于conflig中含有signal在这里执行函数

 // 取消请求相关
if (config.cancelToken || config.signal) {
  if (config.signal) {
    config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
  }
}

3.由于请求未被终止过signal.aborted === false,执行上面的取消函数onCanceled(),如果没有也会执行监听abort事件执行取消函数onCanceled()

AbortController.signal参考链接

4.执行onCanceled()

onCanceled = cancel => {
  // 请求就结束时,取消无效
  if (!request) {
    return;
  }
  // 否则就reject出去
  reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
  // 调用xml的abort()取消方法
  request.abort();
  //把这次请求的xml置为空
  request = null;
};