[Source code analysis] axios

foreword

It’s been a long time since I wrote a blog. It’s not that I’m lazy. It’s that I just changed jobs recently. I’m busy familiarizing myself with business and new life, and haven’t found the right pace to move on.

What I want to share today is axiosthe source code. The reason is that I originally wanted to learn ts, and then there was tsa development one in a certain class axios, and then I couldn’t understand it when I saw the middle, so I just studied axiosthe source code by myself, so I have this summary.

Functional Analysis

Referring to the documentation and source code, I have summarized the following functions:

  • Send requests, support browser environment and Nodeenvironment;
  • create Axiosinstance;
  • There are request and response interceptors;
  • Support conversion of request data and response data
  • Support cancel function;
  • Supports concurrent functions, etc.

directory analysis

- lib
	- adapters      # 请求封装,适配浏览器和node环境
	- cancel        # 跟取消相关
	- core          # 核心内容
	- helpers       # 工具类函数(可以独立于axios使用)
	- axios.js      # index.js中就是使用了这个文件
	- default.js    # 默认配置项
	- utils.js      # 工具类函数

Source code analysis

entry file

package.jsonIt can be seen from this index.jsin , and index.jsthe file of is introduced lib/axios.js:

function createInstance(){
    
     /** ..  */  } 

// 创建Axios实例
var axios = createInstance(defaults);
// 便于后续继承Axios
axios.Axios = Axios;

// 跟取消请求有关
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// 跟并发请求有关
axios.all = function all(promise){
    
     /** ..  */ }
axios.spread = require('./helpers/spread');

// 判断请求错误
axios.isAxiosError = require('./helpers/isAxiosError');

module.exports = axios;
module.exports.default = axios;

tool method

In the process of reading the source code, it is strongly recommended that you go through the code in the folder utils.jsand folder first:helpers

// utils.js

// ...

module.exports = {
    
    
  isArray: isArray, 
  isArrayBuffer: isArrayBuffer,
  isBuffer: isBuffer,
  isFormData: isFormData,
  isArrayBufferView: isArrayBufferView,
  isString: isString,
  isNumber: isNumber,
  isObject: isObject,
  isPlainObject: isPlainObject,
  isUndefined: isUndefined,
  isDate: isDate,
  isFile: isFile,
  isBlob: isBlob,
  isFunction: isFunction,
  isStream: isStream,
  isURLSearchParams: isURLSearchParams,
  isStandardBrowserEnv: isStandardBrowserEnv,
  forEach: forEach,   // 遍历对象或者数组
  merge: merge,       // 将A、B两个变量进行合并
  extend: extend,     // A继承B的属性
  trim: trim,
  stripBOM: stripBOM
};

utils.jsIt mainly provides some isbeginning functions to judge whether it is a variable of a certain type or format, as well as tool functions such as traversal, merger, and inheritance.

helpersIn the file there is:

- bind.js          # 封装了一个类似bind的方法
- buildURL.js      # 将params参数拼接在url上
- combineURLs.js   # 组合获得新的url
- cookies.js       # 针对不同环境,对cookie的读、写、删除的封装
- deprecatedMethod.js # 对废弃方法的警告
- isAbsoluteURL.js # 判断是否是绝对路径
- isAxiosError.js  # 判断是否是Axios执行过程中抛出的异常
- isURLSameOrigin.js # 针对不同环境,判断是否跨域
- normalizeHeaderName.js # 格式化header的key
- parseHeaders.js  # 格式化header的参数并封装在一个对象中
- spread.js  # axios.spread的封装
- validator.js # 校验方法

Default Config

When creating an instance, we will pass a default configinto it:

var defaults = {
    
    

  transitional: {
    
    
    silentJSONParsing: true, // 是否忽略JSON Parse的报错
    forcedJSONParsing: true, // resposeType不是json的情况下是否将响应值改为false
    clarifyTimeoutError: false // 超时的时候是否用ETIMEDOUT代替ECONNABORTED
  },

  adapter: getDefaultAdapter(),  // 请求方法(浏览器环境就是发起XMLHttpRequest)
  
  // 对请求数据进行转换
  transformRequest: [function transformRequest(data, headers) {
    
    
    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;
    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,
  
  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。
  // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
  // promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function validateStatus(status) {
    
    
    return status >= 200 && status < 300;
  }
};

defaults.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 class

In createInstance(), the source code is at new Axios():Axioscore/Axios.js

// core/Axios.js
function Axios(instanceConfig) {
    
    
  // 配置项
  this.defaults = instanceConfig;
  // 拦截器
  this.interceptors = {
    
    
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

// 核心!!! 请求方法
Axios.prototype.request = function request(config) {
    
    }

// 获得uri
Axios.prototype.getUri = function getUri(config) {
    
    }

// 根据request扩展出不同方法下的请求
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: method,
      url: url,
      data: (config || {
    
    }).data
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
    
    
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    
    
    return this.request(mergeConfig(config || {
    
    }, {
    
    
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

interceptor

Before looking at requestthe implementation, let's focus on the interceptor:

function Axios(instanceConfig) {
    
    
  // 配置项
  this.defaults = instanceConfig;
  // 拦截器
  this.interceptors = {
    
    
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

InterceptorManageris defined in core/InterceptorManager.js:

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

// use是添加拦截器,其中fulfilled和rejected对应的就是promise的那两个
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;
};

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

// forEach遍历拦截器
InterceptorManager.prototype.forEach = function forEach(fn) {
    
    
  utils.forEach(this.handlers, function forEachHandler(h) {
    
    
    if (h !== null) {
    
    
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

The core of the interceptor is to define an handlersarray. Every time an interceptor is added, a fulfilled、rejected、synchronous和runWhennew object is added, and then placed in handlersthe array. If it is to be deleted, the corresponding object is handlers[index]set to empty, so that it will not be executed when traversing.

request request

Axios.prototype.request = function request(config) {
    
    
  // 支持axios(url[, config])
  if (typeof config === 'string') {
    
    
    config = arguments[1] || {
    
    };
    config.url = arguments[0];
  } else {
    
    
  // 支持axios(config)
    config = config || {
    
    };
  }
  
  // 将用户的config和默认config进行合并
  config = mergeConfig(this.defaults, config);

  // 设置method
  if (config.method) {
    
    
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    
    
    config.method = this.defaults.method.toLowerCase();
  } else {
    
    
    config.method = 'get';
  }
  
  // 配置transitional
  var transitional = config.transitional;

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

  // 添加请求拦截器,顺序是倒叙的
  // 比如 调用 A B C接口,那么请求拦截器的顺序为[C、B、A]
  var requestInterceptorChain = [];
  var synchronousRequestInterceptors = true;
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    
    
    if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
    
    
      return;
    }

    synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  
  // 添加响应拦截器,顺序是倒正叙的
  // 比如 调用 A B C接口,那么请求拦截器的顺序为[A、B、C]
  var responseInterceptorChain = [];
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    
    
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  });

  var 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;
  }

  // 如果是同步的话
  // 先执行 请求拦截器
  // 执行请求
  // 执行 响应拦截器
  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;
};

Axios initiates the request directly

Encapsulated above request, it is not difficult to understand createInstance:

function createInstance(defaultConfig) {
    
    
  var context = new Axios(defaultConfig);
  // 把request直接赋值给instance
  var instance = bind(Axios.prototype.request, context);

  utils.extend(instance, Axios.prototype, context);

  utils.extend(instance, context);

  instance.create = function create(instanceConfig) {
    
    
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

cancel request

There are three files related to cancellation in the source code:

// cancel/Cancel.js

// Cancel就是一个含有message属性的类
function Cancel(message) {
    
    
  this.message = message;
}

Cancel.prototype.toString = function toString() {
    
    
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;
// cancel/CancelToken.js

function CancelToken(executor) {
    
    
  if (typeof executor !== 'function') {
    
    
    throw new TypeError('executor must be a function.');
  }

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

  var token = this;
  executor(function cancel(message) {
    
    
    if (token.reason) {
    
    
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}


CancelToken.prototype.throwIfRequested = function throwIfRequested() {
    
    
  if (this.reason) {
    
    
    throw this.reason;
  }
};

CancelToken.source = function source() {
    
    
  var cancel;
  var token = new CancelToken(function executor(c) {
    
    
    cancel = c;
  });
  return {
    
    
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;
// cancel/isCancel.js

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

In fact, I have never used the function of canceling the request. When I read Reactthe relevant brochure, the author mentioned a practical case:

In the process of real-time search, in addition to using anti-shake/throttling, you can also use cancel requests, because at the time of the last search, the previous request may not have ended. At this time, you can cancel the previous request before initiating the request.

Here is an actual use case written by another author: the method of canceling requests and preventing repeated requests in axios


If there are any mistakes, please point them out, thank you for reading~

Guess you like

Origin blog.csdn.net/qq_34086980/article/details/120499420