Axios-源码

195 阅读13分钟

版本

v0.26.1 官方文档

项目架构

.
├── dist                                ## 项目最终打包数据目录
│   ├── axios.js
│   ├── axios.map
│   ├── axios.min.js
│   └── axios.min.map
├── index.d.ts                          ## ts类型声明输出
├── index.js                            ## 入口文件
├── lib                                 ## 项目源码库
    ├── adapters                        ## 定义发送请求的适配器
    │   ├── http.js
    │   └── xhr.js 
    ├── axios.js                        ## 请求入口,导出方法
    ├── cancel                          ## 定义取消请求方法
    │   ├── Cancel.js
    │   ├── CancelToken.js
    │   └── isCancel.js
    ├── core                            # 核心包
    │   ├── Axios.js
    │   ├── InterceptorManager.js
    │   ├── buildFullPath.js
    │   ├── createError.js
    │   ├── dispatchRequest.js
    │   ├── enhanceError.js
    │   ├── mergeConfig.js
    │   ├── settle.js
    │   └── transformData.js
    ├── defaults                       ## 默认配置
    │   ├── index.js
    │   └── transitional.js
    ├── env                            ## env info
    │   └── data.js 
    ├── helpers                        ## 定义辅助方法
    │   ├── bind.js
    │   ├── buildURL.js
    │   ├── combineURLs.js
    │   ├── cookies.js
    │   ├── deprecatedMethod.js
    │   ├── isAbsoluteURL.js
    │   ├── isAxiosError.js
    │   ├── isURLSameOrigin.js
    │   ├── normalizeHeaderName.js
    │   ├── parseHeaders.js
    │   ├── spread.js
    │   ├── toFormData.js
    │   └── validator.js
    └── utils.js                      ## 常用工具方法

XMLHttpRequest 常用重要Api

  • onreadystatechange
  • readyState
  • responseType
  • response
  • status
  • setRequestHeader
  • abort
  • getAllResponseHeaders

思考

分析源码前,我们从对外Api开始入口,思考使用的几种方式,一些场景 从上文axios介绍了解到,大概有以下几种方法

  • 调用方式
 // 函数执行
 axios(config)
 
 // 方法调用
 axios[method](config)
 
 // 创建实例
 const instance = axios.create(config);
 ...
 ...
  • 拦截器
    • 请求拦截器
    • 响应拦截器
       const instance = axios.create({
         timeout: 20,
       })
    
      instance.interceptors.request.use(
        (cinfig) => {
          return config
        },
        (err) => Promise.reject(err),
      )
    
      instance.interceptors.response.use(
        (response) => {
          return response
        },
        (err) => Promise.reject(err),
      )
    
  • 取消请求
    • axios.cancelToken
    • AbortController
  • axios.all / axios.spread

Axios执行顺序

WechatIMG88.jpeg

Default 默认配置

var utils = require('../utils');
var normalizeHeaderName = require('../helpers/normalizeHeaderName');
var enhanceError = require('../core/enhanceError');
var transitionalDefaults = require('./transitional');

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}

// 根据当前环境是否存在XMLHttpRequest对象,执行不同适配器方法
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
      // 浏览器环境
    adapter = require('../adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // node环境
    adapter = require('../adapters/http');
  }
  return adapter;
}

function stringifySafely(rawValue, parser, encoder) {
  if (utils.isString(rawValue)) {
    try {
      (parser || JSON.parse)(rawValue);
      return utils.trim(rawValue);
    } catch (e) {
      if (e.name !== 'SyntaxError') {
        throw e;
      }
    }
  }

  return (encoder || JSON.stringify)(rawValue);
}

var defaults = {

  transitional: transitionalDefaults,

  // 根据当前运行环境获取请求适配器
  adapter: getDefaultAdapter(),

   // 改变请求data
  transformRequest: [function transformRequest(data, headers) {
    // 标准化headers中Accept、Content-Type参数
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');

    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
      setContentTypeIfUnset(headers, 'application/json');
      return stringifySafely(data);
    }
    return data;
  }],

  // 改变响应数据
  transformResponse: [function transformResponse(data) {
    var transitional = this.transitional || defaults.transitional;
    var silentJSONParsing = transitional && transitional.silentJSONParsing;
    var forcedJSONParsing = transitional && transitional.forcedJSONParsing;
    var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';

    if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
      try {
        return JSON.parse(data);
      } catch (e) {
        if (strictJSONParsing) {
          if (e.name === 'SyntaxError') {
            throw enhanceError(e, this, 'E_JSON_PARSE');
          }
          throw e;
        }
      }
    }

    return data;
  }],

  // 设置超时时间,默认0不会超时
  timeout: 0,

  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  maxContentLength: -1,
  maxBodyLength: -1,
    
  // 定义给定的http状态码,判断是执行resolve还是Promis.reject
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  },

  headers: {
    common: {
      'Accept': 'application/json, text/plain, */*'
    }
  }
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

入口axios.js

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */

function createInstance(defaultConfig) {
  // 创建Axios实例化对象,包含默认配置<defaults>以及拦截器<this.interceptors>、
  // 挂在原型对象上的请求方法<Axios.prototype.request>
  var context = new Axios(defaultConfig);
  
  // 创建新的实例对象, bind返回一个函数,函数内部执行Axios.prototype.request同时函数上下文为context
  var instance = bind(Axios.prototype.request, context);

  // 复制Axios.prototype原型对象上属性、方法给新的实例insatance
  // utisl.extend内部实现, 如果是复制是方法,那么改变方法内部函数执行上下文为context
  utils.extend(instance, Axios.prototype, context);

  // 复制context属性方法给instance
  utils.extend(instance, context);

  // 工厂方法,支持通过create传入自定义配置
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

// 创建默认导出的实例对象
var axios = createInstance(defaults);

// 暴露Axios允许去继承???
axios.Axios = Axios;

// 暴露CancelToken/isCancel/VERSION 方法
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.VERSION = require('./env/data').version;

// 暴露 all/spread方法, 类似Promise.all功能
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

// 暴露 isAxiosError方法
axios.isAxiosError = require('./helpers/isAxiosError');

module.exports = axios;

// 允许在ts中使用默认导入
module.exports.default = axios;

入口文件axios.js大致流程

  • 利用构造函数Axios,实例化对象context,context包含一些默认配置defaults和管理拦截器
  • 创建新的实例对象instance,同时把Axios.prototype.request执行上下文改造为context
  • 将Axios.prototype原型对象属性及方法复制到intance上,方法中this指向了context
  • 把context上默认属性以及拦截器复制到instance上
  • 利用工厂函数,创建新的实例,用户可以传入自定义配置

Axios.js

'use strict';

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
var validator = require('../helpers/validator');

var validators = validator.validators;

// Axios构造函数,传入默认配置,拥有拦截器对象
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

// 核心方法 派发请求
Axios.prototype.request = function request(configOrUrl, config) {
   // 处理config, configUrl
  if (typeof configOrUrl === 'string') {
    config = config || {};
    config.url = configOrUrl;
  } else {
    config = configOrUrl || {};
  }

  config = mergeConfig(this.defaults, config);

  // 设置请求方法
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  var transitional = config.transitional;

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

  // 请求拦截器数组
  var requestInterceptorChain = [];
  
  // 默认设置是同步请求拦截器
  var synchronousRequestInterceptors = true;
  
  // 遍历注入的请求拦截器: 内部调用utils.forEach过滤掉原型对象上属性,只剩this.handles拦截器数组
  // this.interceptors.request ===  new InterceptorManager() 
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
      return;
    }

    // 对外抛出异步||同步配置,通过synchronous标志拦截器是同步还是异步,默认是异步
    synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

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


  var promise;
  // 组成promise调用链
  // 异步请求拦截器,默认情况
  if (!synchronousRequestInterceptors) {
    var chain = [dispatchRequest, undefined];

    Array.prototype.unshift.apply(chain, requestInterceptorChain);
    chain = chain.concat(responseInterceptorChain);

    promise = Promise.resolve(config);
    while (chain.length) {
      promise = promise.then(chain.shift(), chain.shift());
    }

    return promise;
  }


  // 同步请求拦截器,一次调用请求拦截器,得到最后处理后newCondfig
  var newConfig = config;
  while (requestInterceptorChain.length) {
    var onFulfilled = requestInterceptorChain.shift();
    var onRejected = requestInterceptorChain.shift();
    try {
      newConfig = onFulfilled(newConfig);
    } catch (error) {
      onRejected(error);
      break;
    }
  }

  // 发起请求
  try {
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }

  while (responseInterceptorChain.length) {
    promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
  }

  return promise;
};

// 获取uri函数
Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// 依次遍历方法别名,Axios.prototype原型对象别名请求,核心都是调用Axios.prototype.request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data
    }));
  };
});

// post, put, patch 请求需要请求体<data>, 分开处理
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

Axios.j大致流程

  • 构造函数可以实例化对象,挂在defaults,拦截器对象
  • Axios.prototype挂在别名方法,内部都是调用this.request
  • 组装promise调用链
    var promiseChain = [
      '请求成功拦截2', '请求失败拦截2',  
      '请求成功拦截1', '请求失败拦截1',  
      'dispatch',  'undefined',
      '响应成功拦截1', '响应失败拦截1',
      '响应成功拦截2', '响应失败拦截2',
    ]
  • 调用Dispatch
Promise调用链

无标题-2022-04-20-2148.png

拦截器

'use strict';

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

function InterceptorManager() {
  this.handlers = [];
}

 // 添加拦截器到this.handlers, 返回当前索引值,用作移除拦截器使用
 // const current = axios.interceptors.request.use(fn);
 // axios.interceptors.request.eject(current);
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null
  });
  return this.handlers.length - 1;
};

// 移除拦截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

 // 遍历所有非空注册拦截器,
 // 拦截器被移除后 this.handlers[id] = null; 拦截器数组被移除项索引为null,需要过滤掉
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

异步拦截器执行顺序

  const fetch = async () => {
    try {
      const res = await instance({ url: '/api' })
      console.log(res)
    } catch (err) {
      console.log('fn', err)
    }
  }
  
  const instance = axios.create()

instance.interceptors.request.use(
  async (config) => {
    console.log('第1个请求拦截器执行 async start')

    const res = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(true)
      }, 5000)
    })

    console.log('第1个请求拦截器执行 async end')
    if (!res) {
      return Promise.reject(new Error('request1 error'))
    }

    return config
  },
  (err) => {
    console.log('第1个请求拦截器', err)

    return Promise.reject(err)
  },
)

instance.interceptors.request.use(
  async (config) => {
    console.log('第2个请求拦截器执行 async start')
    const res = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(true)
      }, 5000)
    })

    console.log('第2个请求拦截器执行 async end')
    if (!res) {
      return Promise.reject(new Error('request2 error'))
    }

    return config
  },
  (err) => {
    console.log('第2个请求拦截器', err)

    return Promise.reject(err)
  },
)

instance.interceptors.response.use(
  (res) => {
    console.log('第1个响应拦截器', res)

    return res
  },
  (err) => Promise.reject(err),
)

instance.interceptors.response.use(
  (res) => {
    console.log('第2个响应拦截器', res)

    return res
  },
  (err) => Promise.reject(err),
)

**请求拦截器全部成功**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器执行 async end
// 第1个请求拦截器执行 async start
// 第1个请求拦截器执行 async end
...10s后发起请求
// 第1个响应拦截器 {data: {…}, status: 200, statusText: 'OK', headers: {…}, config: {…}, …}
// 第2个响应拦截器 {data: {…}, status: 200, statusText: 'OK', headers: {…}, config: {…}, …}

**第二个拦截器reject**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器执行 async end
...delay 5s
// 第1个请求拦截器 Error: request2 error

**第一个拦截器reject**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器执行 async end
...delay 5s
// 第1个请求拦截器执行 async start
// 第1个请求拦截器执行 async end
...delay 5s
// fn Error: request1 error

同步拦截器执行顺序

const instance = axios.create()

instance.interceptors.request.use(
  (config) => {
    console.log('第1个请求拦截器执行 async start')
    const res = true
    if (!res) {
      throw new Error('request1 error')
    }

    return config
  },
  (err) => {
    console.log('第1个请求拦截器', err)

    throw new Error(err)
  },
  {
    synchronous: true,
  },
)

instance.interceptors.request.use(
  (config) => {
    console.log('第2个请求拦截器执行 async start')
    const res = true
    if (!res) {
      throw new Error('request2 error')
    }

    return config
  },
  (err) => {
    console.log('第2个请求拦截器', err)

    throw new Error(err)
  },
  {
    synchronous: true,
  },
)

instance.interceptors.response.use(
  (res) => {
    console.log('第1个响应拦截器', res)

    return res
  },
  (err) => Promise.reject(err),
)

instance.interceptors.response.use(
  (res) => {
    console.log('第2个响应拦截器', res)

    return res
  },
  (err) => Promise.reject(err),
)

**都是true**
// 第2个请求拦截器执行 async start
// 第1个请求拦截器执行 async start
// 第1个响应拦截器
// 第2个响应拦截器

**第二个拦截false**
// 第2个请求拦截器执行 async start
// 第2个请求拦截器 Error: request2 error
// fn Error: Error: request2 error // 是外部方法捕获

**第一个拦截false**
// 第2个请求拦截器执行 async start
// 第1个请求拦截器执行 async start
// 第1个请求拦截器 Error: request1 error
// fn Error: Error: request1 error // 是外部方法捕获

Dispatch派发请求

'use strict';

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var Cancel = require('../cancel/Cancel');

// 如果已经取消,直接抛出原因
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    throw new Cancel('canceled');
  }
}

// 派发请求,可以使用自定义适配器
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // 确保header是存在
  config.headers = config.headers || {};

  // 转换请求数据,post
  config.data = transformData.call(
    config,
    config.data,
    config.headers,
    config.transformRequest
  );

  // 抹平header
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );

  // 删除header上挂在的方法
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // 使用默认适配器
  var adapter = config.adapter || defaults.adapter;

  // 自定义适配器,方法接收config参数,返回Promise
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

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

    return response;
  }, function onAdapterRejection(reason) {
     // 如果是手动取消请求
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // 转化数据
      if (reason && reason.response) {
        reason.response.data = transformData.call(
          config,
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

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

Dispacth主要做了下面几件事情

  • 如果已经取消请求,则取消请求,throw 原因,promise走向reject
  • 转化请求数据
  • 抹平header,删除header上一些方法
  • 使用适配器请求发起请求,返回Promis实例

WechatIMG89.jpeg

Adapter 适配器

'use strict';

var utils = require('./../utils');
var settle = require('./../core/settle');
var cookies = require('./../helpers/cookies');
var buildURL = require('./../helpers/buildURL');
var buildFullPath = require('../core/buildFullPath');
var parseHeaders = require('./../helpers/parseHeaders');
var isURLSameOrigin = require('./../helpers/isURLSameOrigin');
var createError = require('../core/createError');
var transitionalDefaults = require('../defaults/transitional');
var Cancel = require('../cancel/Cancel');

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;
    var responseType = config.responseType;
    var onCanceled;
    function done() {
      if (config.cancelToken) {
        config.cancelToken.unsubscribe(onCanceled);
      }

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

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; 
    }

    var request = new XMLHttpRequest();

    // http 授权验证相关
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }
    
    // 拼接完整路径
    var fullPath = buildFullPath(config.baseURL, config.url);
    
    // 序列化请求params参数,eg:get请求params参数
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // 设置超时时间
    request.timeout = config.timeout;

    // 当请求结束时触发, 无论请求成还是失败<abort||error>
    function onloadend() {
      if (!request) {
        return;
      }
      // 生成数据
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      var responseData = !responseType || responseType === 'text' ||  responseType === 'json' ?
        request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      // 见utils.settle函数。 对于给定状态码判断响应是成功还是失败
      settle(function _resolve(value) {
        resolve(value);
        done();
      }, function _reject(err) {
        reject(err);
        done();
      }, response);

      // 请求结束清除request
      request = null;
    }

    if ('onloadend' in request) {
      request.onloadend = onloadend;
    } else {
      // 监听http状态值去仿真onloadend
      request.onreadystatechange = function handleLoad() {
        if (!request || request.readyState !== 4) {
          return;
        }

        // 对于大数据请求错误并不会低到response, 请求错误在request.onerror事件捕获
        // 但是有一个例外: 使用file协议的请求,大多数浏览器即使请求成功,也会状态码为0
        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'
        setTimeout(onloadend);
      };
    }

    // request被停止时触发
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      request = null;
    };

    // 处理请求错误
    request.onerror = function handleError() {
      reject(createError('Network Error', config, null, request));
      request = null;
    };

    // 处理超时
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
      var transitional = config.transitional || transitionalDefaults;
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(
        timeoutErrorMessage,
        config,
        transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED',
        request));

      request = null;
    };

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

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // 添加请求头
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
       // requestData是undefined情况下,移除content-type
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') 
          delete requestHeaders[key];
        } else {
          request.setRequestHeader(key, val);
        }
      });
    }

    // 传递cookie
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

    // 添加responseType
    if (responseType && responseType !== 'json') {
      request.responseType = config.responseType;
    }

    // 下载进度处理时间:在请求完整之前周期性调用callback,
    // 可以用来判断当前请求进度,只做进度条
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // 上传进度处理事件: 并不是所有浏览器都支持
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    // 处理拦截器
    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);
      }
    }

    if (!requestData) {
      requestData = null;
    }

    // 发送请求
    request.send(requestData);
  });
};

适配器主要做以下事情

  • 新建XMLHttpRequest实例
  • 拼接完整路径,处理header,生成响应数据
  • 设置request.onerror、ontimeout事件处理器
  • 对onreadystatechange || request.onloaded事件监听
  • 配置onUploadProgress、onDownloadProgress处理上传和下载进度
  • 提供取消功能,调用原生abort方法

拦截器CancelToken

xhs.js取消请求部分代码

    if (config.cancelToken || config.signal) {
      onCanceled = function(cancel) {
        if (!request) {
          return;
        }
        reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
        request.abort();
        request = null;
      };

      // 配置cancelToken,则把onCanceled函数维护在实例化CancelToken对象的订阅者列表里面
      config.cancelToken && config.cancelToken.subscribe(onCanceled);
      // 不支持外部传入自定义输入参数
      if (config.signal) {
      // config.signal.aborted 表示与之通信的请求是否被中止, 中止返回true, 否则false
      // 倘若进入请求已经中止则直接调用onCanceled(), 否则信号对象abortSignal添加监听事件abort
      // 中止器对象AbortController调用abort方法触发信号对象abortSignal的abort监听函数, 进入执行onCanCanl方法
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }


'use strict';

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

// CanToken 构造函数,接受executor<执行方法>函数作为参数
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;

  // 创建promise实例
  this.promise = new Promise(function promiseExecutor(resolve) {
     // resolve方法提出来,resolvePromise执行时,this.promis状态变成fullfilled
    resolvePromise = resolve;
  });

  var token = this;
  
  // resolvePromise执行,进行then阶段
  this.promise.then(function(cancel) {
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;

    // 遍历订阅列表
    // 将cancel取消原因执行订阅函数
    // 订阅者是 xhr中 存入的config.cancelToken && config.cancelToken.subscribe(onCanceled);
    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });

  this.promise.then = function(onfulfilled) {
    var _resolve;
    var promise = new Promise(function(resolve) {
      token.subscribe(resolve);
      _resolve = resolve;
    }).then(onfulfilled);

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

    return promise;
  };

  // 立即执行实例化过程中传入的函数。把取消函数cancel抛给外部,把取消时机给用户
  // 执行cancel函数
  executor(function cancel(message) {
    // 如果已经取消,则返回
    if (token.reason) {
      return;
    }
    
    // 调用new Cancel(message),有__CANCEL__属性,标志主动取消请求,可自定义取消信息,赋值给token<this>上reson
    token.reason = new Cancel(message);
    // 执行this.promise中resolve。promise变成完成态
    resolvePromise(token.reason);
  });
}

// 已经取消则返回取消原因
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

// 订阅取消信号,把取消函数存放在this._listeners订阅数组中
CancelToken.prototype.subscribe = function subscribe(listener) {
  if (this.reason) {
    listener(this.reason);
    return;
  }

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

// 取消订阅
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实例化对象和cancel函数组成
// 最终要分析实例化CancelToken过程中executor执行规则
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;

取消请求方法

CancelToken

  // 方式一: CancelToken.source();
  const source = cancelToken.source()
  const fetch = async () => {
    try {
      const res = await axios({
        url: '/api',
        params: {
          name: 1,
        },
        cancelToken: source.token,
        onDownloadProgress: (event) => {
          console.log(event)
        },
      })
      console.log(res)
    } catch (err) {
      if (axios.isCancel(err)) {
        console.log('cancel error')
      }
      console.log('fn', err)
    }
  }

  const cancel = () => {
    source.cancel()
  }
  
  
  // 方式二: 构造函数实例化
  const fetch = async () => {
    try {
      const res = await axios({
        url: '/api',
        params: {
          name: 1,
        },
        cancelToken: new CancelToken((c) => {
          cancel = c
        }),
        onDownloadProgress: (event) => {
          console.log(event)
        },
      })
      console.log(res)
    } catch (err) {
      if (axios.isCancel(err)) {
        console.log('cancel error')
      }
      console.log('fn', err)
    }
  }

  const cancelFetch = () => {
    cancel()
  }
AbortController
  const Controller = new AbortController()
  const fetch = async () => {
    try {
      const res = await axios({
        url: '/api',
        params: {
          name: 1,
        },
        signal: Controller.signal,
        onDownloadProgress: (event) => {
          console.log(event)
        },
      })
      console.log(res)
    } catch (err) {
      if (axios.isCancel(err)) {
        console.log('cancel error')
      }
      console.log('fn', err)
    }
  }

  const cancelFetch = () => {
    Controller.abort()
  }

WechatIMG90.png

总结

CancelToken构造函数内部执行会创建一个promise实例和一个reason存储取消信息,实例化过程执行executor,executor会把取消方法抛给外部,当外部调用取消方法。promise进入完成态,执行订阅列表<这个订阅列表是xhr请求创建过程中维护的>。当订阅函数执行,xhr中promise调用链执行reject并调用原生方法abort取消请求

signal
从v0.22.0开始,axios支持以fetch api传入取消信号sinal<AbortController构造函数实例>中止请求

CancelToken以及signal都是间接调用request.abort()中止请求。

AbortController

AbortController接口是一个控制器对象,允许你根据需要中止一个或多个web请求

描述: 调用AbortController构造函数,得到实例化对象 中止器对象abortController。 在fetch请求中,将abortController.signal变量添加到请求体中。使用abortController.abort()中止请求;

  • const abortController = new AbortController() 中止请求对象 => 中止器对象
  • abortSignal = abortController.signal 信号对象。挂在属性aborted && 设置abort监听函数
  • 发出请求 abortSignal传入fetch/axios/... 请求体 {signal: abortSignal}
  // 取消重复请求: 例如弱网情况下,频繁切换Tab选项卡,或者频繁Input输入请求
  const abortController = useRef<AbortController>(null)
  console.log('dashboard')

  const fetch = async () => {
    if (abortController.current?.signal) {
      abortController.current.abort()
    }

    try {
      abortController.current = new AbortController()

      const res = await axios({
        url: '/api',
        params: {
          name: 1,
        },
        signal: abortController.current.signal,
        onDownloadProgress: (event) => {
          console.log(event)
        },
      })
      console.log(res)
      abortController.current = null
    } catch (err) {
      if (axios.isCancel(err)) {
        console.log('cancel error')
      }
      console.log('fn', err)
    }
  }