Axios源码解析

时间:2021-2-20 作者:admin

引言

    之前看了一篇Axios源码解析的文章,觉得挺不错的,就自已去看了下Axios的源码。下面,我主要讲解请求的配置、发起get、post等请求时发生了什么以及有请求拦截和响应拦截时又对请求做了什么。

关于config

    我们要明白,当我们引入axios的时候,他是有默认配置的,他的默认配置就存在axios对象的defaults属性里,由在Axios构造函数实例化的时候引入参数,给defaults赋值。下面就是Axios构造函数。

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

    具体的默认配置的话,在源码的defaults.js文件里,下面是一部分内容,让大家有个大概的了解。

var defaults = {
  adapter: getDefaultAdapter(),

  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)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],

  /**
   * A timeout in milliseconds to abort a request. If set to 0 (default) a
   * timeout is not created.
   */
  timeout: 0,

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

  maxContentLength: -1,
  maxBodyLength: -1,

  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  }
};

    默认配置有了,那么剩下的就是我们自已弄的配置了,默认配置 + 用户配置 = 最终配置。当我们使用post、get等请求时传递的参数有(url, config)或(url, data, config)两种,当然这里的config并不是我所说的 用户配置 ,具体请看源码。

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

    从源码中我们可以知道像get、post等请求方法其实是在axios对象的原型里的,并且他们内部的本质其实都是执行了request方法,request方法的参数其实才是我所说的用户配置,而request方法同样是axios对象的原型里的方法,它前面的this指向的是axios。

    现在默认配置和用户配置都有了,那时是时候让他们合体进化了,下面有请request方法登场。

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

//看下面一行,defaults与我们传过来的config结合了,结合成的config才是最后的配置。
  config = mergeConfig(this.defaults, config);

  // Set 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';
  }

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

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

  return promise;
};

    默认配置和用户配置结合之后就是最终配置,那么config算告一段落了。下面让我们来看看发起请求时做了什么。

请求过程解析

    我们之前说过了get、post等请求方法内部的本质其实都是执行了request方法,所以让我们再看看request方法的代码。

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set 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';
  }

  // Hook up interceptors middleware
  //看下面两行,注意我旁边标注的1和2
  var chain = [dispatchRequest, undefined];     //1
  var promise = Promise.resolve(config);        //2

  //从这里开始的两段代码先不看,我会在Axios源码解析(二)讲解
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  //到这里不看

  //看下面几行,注意我旁边标注的3和4
  while (chain.length) {                                        //3
    promise = promise.then(chain.shift(), chain.shift());       //4
  }

  return promise;
};

    看到我标的1,2,3,4了吧,下面我就直接说是第几行了。在第2行里变量promise是带有最终配置config的Primise对象,再看第4行,then的参数是从chain变量获取的,也就是说dispatchRequest是一个函数,并且它获取了promise对象携带的config并执行,他才是真正发起请求的地方。下面看看dispatchRequest的源码。

传给参数config就是我之前提到的promise携带的config
function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Ensure headers exist
  config.headers = config.headers || {};

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

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

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

 //从config中拿到适配器
  var adapter = config.adapter || defaults.adapter;

 //给适配器传递参数config发起请求,并对返回结果进行处理
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

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

    //把响应数据抛出,其实是return出一个带有响应数据的promise对象,再往上看10行左右,那里还有一个return那里才是真正的return出去
    //如果认为return出去的只是响应数据,建议去看一下promise知识,温故而知新。
    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

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

    大家仔细看看我在代码中的注释,如果一切正常那么一个带有响应数据的promise对象会被return出去,那么看回request方法的源码,在我标4的地方,promise变量的值就是带有响应数据的promise对象,后面还把promise变量return出去了,这次return出去的就是我们在使用axios.post()或axios.get()时,得到的promise对象了。

    这些就是发起请求的过程,是不是觉得自已懂了,但是其实你还不够懂,以上内容是不考虑请求拦截和相应拦截的情况的,之前有一段源码上,我提到先不看就是因为涉及到了它,现在让我们继续探索吧。

拦截

    关于拦截,我们先看一下构造函数Axios。

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

    注意到了吗?request和response属性都是InterceptorManager构造函数实例化的对象,在书写拦截代码时,我们经常这样写axios.interceptors.request.use(fulfilled, rejected),但是我们不知道发生了什么,现在让我们看看InterceptorManager构造函数。

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

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

    这段代码很简单,我们可以清晰地看到,InterceptorManager的实例化对象是有handlers属性的,每次我们使用use方法时传的两个函数,就是先放到一个对象上,然后push到handlers数组中。

    虽然我们知道了use方法的本质,但这还没结束,我们都还没明白她是什么时候执行的呢。关于这个问题,我们就要回到之前的request方法,也就是我说先不要看的地方,是时候解开谜题了。

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set 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';
  }

  //看这里一下的地方
  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

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

  return promise;
};

    其实chain并没有之前讲得那么简单,InterceptorManager有个forEach属性方法,上面的代码也运用了,而作为参数的函数,他的interceptor参数是之前提到的handlers数组的数据,handlers数组的数据被放到了chain数组里(注意:handlers数组的数据是包含两个函数的对象,放到chain数组时,是一个函数占chain数组的一个位置,1:2)。

    当然forEach方法里有一个utils.forEach方法是不清楚的,我展示一下。

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

    还有,如果你观察仔细的话,你会发现,请求拦截的handlers是被放到chain开头的,而响应拦截的handlers是被放到chain结尾的,就像下图一样。

    现在我们假设有一个请求拦截和响应拦截,也就是上图所展示的情况。那么request方法的最后一部分就要重新审视下了。

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

    在这个循环里,携带了config的promise拿取chain数组的两个数据(也就是函数)作为then的参数,执行完后,又再次拿取执行,以此循环,直到chain没有数据,才跳出循环,其实就像是promise链。其实,说实在的,这里可以这样看。

  1. 请求拦截从携带了config的promise中拿到config,进行处理后,return出一个携带了config的promise
  2. 请求发起函数dispatchRequest从第一步中return出的promise拿到config,发起请求,最后return出一个携带了response的promise
  3. 响应拦截从第二步中return出的promise拿到response,进行处理后,return出一个携带了response的promise

    现在可以说完结了,但是要提醒的是,如果有多个请求拦截,先定义的请求拦截反而是后执行的,至于为什么,这个我知道,但我不告诉你,233333。这就当作今天的作业,自已去思考吧,提醒一下,请求拦截是从chain数组的头部插入。

    谢谢你的阅读,如果觉得不错,请点个赞,收藏一下就更好了。你点点赞,和收收藏,是对我最好的鼓励,谢谢。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。