Axios源码学习记录

591 阅读1分钟

概述

我们引入的 axios 实际上是Axios的原型方法 request (函数对象),该函数的执行上下文是 Axios 的一个实例对象。

  • 该实例扩展了 Axios 构造函数原型上的方法,以及公有属性 defaultsinterceptors
  • 暴露了 axios.Axios 以允许其他的类进行继承。
  • 提供了 create 方法用于创建新实例的工厂函数。
  • 提供了CancelCancelTokenisCancel用于中断请求。
  • 提供了allspread方法用于同时发送多个请求。

createInstance()

// lib/axios.js
/**
 * 创建一个 Axios 实例
 * @param {Object} defaultConfig 实例的默认配置 
 * @return {Axios} 返回一个新的Axios实例
 */
function createInstance(defaultConfig){
  // 创建一个axios 实例 主要是当作 axios 执行的上下文,下面的扩展的方法都会将该实例绑定为 this 
  // 为什么要这么做呢?
  // 假如不这样做,只是创建一个实例,通过原型方法实现有什么区别?
  var context = new Axios(defaultConfig)
  
  // 将 request 方法作为 instance 实例导出为 axios (实际是request方法)
  var instance = bind(Axios.prototype.request, context)
  
  // 将Axios原型上的方法扩展到实例上 并绑定函数的 this 为 上面创建的context实例。
  Utils.extend(instance, Axios.prototype, context)
  
  // 将 Axios 构造函数的公有属性扩展到该实例上
  Utils.extend(instance, context)
  
  return instance
}

var axios = createInstance(defaults);
module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;
// lib/helpers/bind.js

function bind(fn, thisArg){
  return function warp(){
    var args = new Array(arguments.length);
    for(var i = 0; i < args.length; i++){
    	args[i] = arguments[i]
    }
    return fn.apply(thisArg, args)
  }
}
// lib/utils.js

/**
 * Extends object a by mutably adding to it the properties of object b.
 * 
 * @param {Object} a The object to be extended
 * @param {Object} b The object to copy properties from
 * @param {Object} thisArg The object to bind function to
 * @return {Object} The resulting value of object a
 */
function extend(a, b, thisArg){
  forEach(b, function assignValue(val, key){
    if(thisArg && typeof val === 'function'){
    	a[key] = bind(val, thisArg);
    }else{
    	a[key] = val
    }
  })
}

/**
 * Iterate over an Array or an Object invoking a function for each item.
 *
 * If `obj` is an Array callback will be called passing
 * the value, index, and complete array for each item.
 *
 * If 'obj' is an Object callback will be called passing
 * the value, key, and complete object for each property.
 *
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn){
  if(obj === null && typeof obj === 'undefined'){
  	return 
  }
  
  if(typeof obj !== 'object'){
  	obj = [obj]
  }
  
  if(isArray(obj)){
    for(var i = 0, l = obj.length; i < l; i++){
    	fn.call(null, obj[i], i, obj)
    }
  }else{
    for(var key in obj){
        if(Object.prototype.hasOwnProperty.call(obj, key)){
            fn.call(null, obj[key], key, obj)
        }
    }
  }

}

Axios构造函数

公有属性

  • defaults 用于保存Axios 库的默认配置项
  • interceptors 包含 requestresponse 两个请求拦截器。

原型方法

  • request :用于发送请求的主要方法。
  • getUri :获取请求 Uri
  • delete getheadoptions:请求方法的别名,实际上调用的还是 request 方法。
  • postputpatch:请求方法的别名,实际上调用的还是 request 方法。
// lib/core/Axios.js

function Axios(instanceConfig){
  this.defaults = instanceConfig;
  this.insterceptors = {
  	requset: new InterceptorManager(),
        reqponse: new InterceptorManager()
  }
}

Axios.prototype.request = function request(config){
  
  // 可以使用 axios(config) 或者 axios(url[, config])的调用方式
  // 获取请求的url
  if(typeof config === 'string'){
    config = arguments[1] || {};
    config.url = arguments[0];
  }else{
    config = config || {}
  }
  // 合并请求配置信息
  config = mergeConfig(this.defaults, config)
  
  // 获取请求方法 默认为get
  if(config.method){
  	config.method = config.method.toLowerCase()
  }else if{this.defaults.method}{
  	config.method = this.defaults.method.toLowerCase()
  }else{
  	config.method = 'get'
  }
  
  // 连接拦截器中间件 chain 用于注册promise的回调链
  var chain = [dispatchRequest, undefined];
  // 生成一个成功的promise实例,并将请求配置信息传入
  var promise = Promise.resolve(config);
  
  // 将请求拦截器放入 回调链的最前面
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor){
  	chain.unshift(interceptor.fulfilled, interceptor.rejected)
  })
   // 将响应拦截器放入 回调链的最后面
  this.interceptors.response.forEach(function pushRequestInterceptors(interceptors){
  	chain.push(interceptor.fulfilled, interceptor.rejected)
  })
  
  // 依次将回调函数绑定到promise回调链上 如果有报错,根据promise的异常传透原理,会将异常依次抛到下一层
  // promise.then(请求拦截器).then(发送请求).then(响应拦截器)
  while(chain.length){
  	promise = promise.then(chain.shift(), chain.shift())
  }
  
  return promise
}
// 
Axios.prototype.getUri = function getUri(config){
    config = mergeConfig(this.defaults, config)
    return buildURL(congig.url, config.params, config.paramsSerializer).replace(/^\?/,'')
}
// 原型上添加不含data的请求方法别名
utils.forEach(['get','head','options','delete'], function forEachMethodNoData(method){
    Axios.prototype[method] = function(url, config){
  	return this.request(mergeConfig(config || {}, {
            method: method,
          url: url
        }))
    }
})
// 原型上添加含data的请求方法别名
utils.forEach(['post', 'put', 'petch'], function forEachMethodWithData(method){
    Axios.prototype[method] = function(url, data, config){
  	return this.request(mergeConfig(config || {}, {
            method,
            url,
            data
        }))
  }
})
module.export = Axios

拦截器

Axios为我们提供了请求拦截器和响应拦截器。注册的拦截器会存在 公有属性 interceptorsrequestresponse属性上。通过一个 interceptorManager实例管理。 下面我们来看下 InterceptorManager 构造函数是怎么实现的。

  • 通过调用use 方法向栈中存入拦截器
  • 通过调用 eject 方法移除栈中的拦截器
  • 通过调用 forEach 方法遍历栈中的拦截器,并注册拦截器。
function InterceptorManager(){
    this.handlers = []
}

/**
 * Add a new interceptor to the stack
 *	向handlers中添加一个新的拦截器
 * @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
 * 返回拦截器的ID 在移除的时候使用
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected){
    this.handlers.push({
  	fulfilled,
        rejected
    })
    return this.handler.length - 1
}
/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id){
    if(this.handlers[id]){
  	this.handlers[id] = null
    }
}
/**
 * Iterate over all the registered interceptors
 * 遍历handlers,注册拦截器
 * 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
 */
InterceptorManager.prototype.forEach = function forEach(fn){
    utils.forEach(this.handlers, function forEachHandler(h){
  	if(h !== null){
            fn(h)
        }
    })
}

dispatchRequest()

该方法是 request() 中用于向服务器发送请求的主要方法。其返回结果也是一个 promise。

function transformData(data, headers, fns){
    utils.forEach(fns, function(fn){
        data = fn(data, headers)
    })
    return data
}

function dispatchRequest(config){
	// 用于让用户手动取消请求
  throwIfCancellationRequested(config)
  
  // 确保请求头存在
  config.headers = config.headers || {};
  // 请求数据转换
  // 将 data 与 headers 传入指定的函数进行数据转换 config.transformRequest 是一个数组
  // 里面包含了默认的请求数据转换方法 以及用户自定义添加的(如果有的话)
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  )
  // 请求头摊平
  confif.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  )
  
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function clearHeaderConfig(){
        delete config.headers[method]
    }
  )
  
  var adapter = config.adapter || defaults.adapter; 
  
  return adapter(config).then(
      function onAdapterResolution(response){
    	throwIfCancellationRequested(config);
      
      // 转换响应数据
      response.data = transformData(
      	response.data,
        response.headers,
        config.transformResponse
      )
      return response
    },
    function onAdapterRejection(reason){
    	if(!isCancel(reason)){
            throwIfCancellationRequestd(config)
        
            // 转换响应数据
            reason.response = transformData(
                    reason.response.data,
              reason.response.headers,
              config.transformResponse
            )
      }
      return Promise.reject(reason)
    }
  )
}

adapter 请求适配器

axios不仅适用于浏览器还适用于 Node.js。所以在请求的方法上也实现了两种方案。

  • 浏览器使用 XMLHttpRequest
  • Node.js使用 httphttps 中间件
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

我们来看一下 XMLHttpRequest 是如何处理的。

// lib/adapters/xhr

module.exports = function xhrAdapter(config){
return new Promise(function dispatchXhrRequest(resolve, reject){
    var requestData = config.data;
    var requestHeaders = config.headers;
    
    // 如果是FormData
    if(utils.isFormData(requestData)){
    	delete requestHeaders['Content-Type']  // 让浏览器自己设置
    }
    
    if(utils.isBlob(requestData) || utils.isFile(requestData) && requestData.type){
    	delete requestHeaders['Content-Type']  // 让浏览器自己设置
    }
    
    // 创建异步请求
    var request = new XMLHttpRequest();
    // HTTP基本身份验证
    if(config.auth){
      var username = config.auth.username || '';
      var password = unescape(encodeURIComponent(config.auth.password)) || '';
      requestHeaders.Authorization = 'Basic' + btoa(username + ':' + password);
    }
    // 拼接完整的请求地址,如果是绝对路径,直接返回。
    var 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;
    
    request.onreadystatechange = function handleLoad(){
      // 0 代理被创建,但还没调用 open()
      // 1 open() 已被调用
      // 2 send() 已被调用,并且头部和状态可获得
      // 3 下载中 responseText 属性已经包含部分数据
      // 4 下载完成
      // 请求状态码 为4 代表请求已完成 下载操作已完成。
     if(!request || request.readyState !== 4){
      	return;
      }
      // 如果请求出错了,我们没有得到响应,将会由 onerror 处理
      // 有一个例外情况,如果请求使用的 file: 协议,大多是浏览器会返回 0 的状态码,尽管请求成功了。
      if(
        request.status === 0 && 
        !(request.responseURL && request.responseURL.indexof('file:') ===0)
      ){
      	return;
      }
      
      // 准备响应数据
      // 获取响应头
      var responseHeaders = 'getAllResponseHeaders' in request ? getAllResponseHeaders() : null
      // 如果响应的类型是 text 则取responseText作为结果值
      var responseData = !config.responseType || config.reponseType === 'text' ? 
          								request.responseText : request.response;
      
      var response = {
      	data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      }
      // 根据响应码来判断 成功 or 失败 用户可以自定义 vaildateStatus
      settle(resolve, reject, response)
      // 清除request 请求
      request = null
    }
    // 处理浏览器取消请求(不是手动取消)
    request.onabort = function handleAbort(){
    	if (!request) {
            return;
        }

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

      // Clean up request
      request = null;
    }
    
    // 处理网速慢时的网络错误
    request.onerror = function handleError(){
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    }
    
    // Handle timeout
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));

      // Clean up 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;
      }
    }
    
    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Remove Content-Type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add header to the request
          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 (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }
    
       // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }
    
    if(config.cancelToken){
    	// 处理取消事件
      config.cancelToken.promise.then(function onCanceled(cancel){
      	if(!request){
            return
        }
        request.abort();
        reject(cancel);
        request = null
      })
    }
    
    if (!requestData) {
      requestData = null;
    }

    // Send the request
    request.send(requestData);
  })
}

我们来看一下 自定义状态码成功与失败的方法处理

function settle(resolve, reject, response){
  var validateStatus = response.config.validateStatus;
  if(!response.status || !validateStatus || validataStatus(response.status)){
  	resolve(response)
  }else{
  	reject(createError(
            'Request failed with status code ' + response.status,
              response.config,
              null,
              response.request,
              response
            )
        )
  }
}
// 默认的validateStatus为
function validateStatus(status){
	return status >= 200 && status < 300
}