Axios 源码分析(五) Axios dispatchRequest 剖析

Axios 源码分析(五) Axios dispatchRequest 剖析

Cocytus Elias 90 2023-04-26

本文参加了每周一起学习200行源码共读活动

上面文章中我们了解到,axios 使用 dispatchRequest 发送请求,dispatchRequest 位于 lib/core/dispatchRequest.js

dispatchRequest 的调用关系可以看下图。

dispatchRequest

dispatchRequest 下有两个方法:throwIfCancellationRequesteddispatchRequest

throwIfCancellationRequested

/**
 * Throws a `CanceledError` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

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

这个方法主要是用来判断当次请求是否已经取消,如已取消,则抛出异常。

如果请求配置中存在 cancelTokensignal ,则表明当次请求已取消。

使用 cancelToken 取消时,抛出的异常是以传入 new CancelTokenexecutor 函数的 message 参数作为异常信息来抛出 CanceledError 异常。

使用 AbortController,即 fetch API 的方式取消请求取消时,是直接抛出 CanceledError 异常。

dispatchRequest

这个方法是请求派发流程,主要分为三部分:请求头及 data 设置、根据当前环境或配置获取请求适配器、请求后响应处理。

请求头设置

/**
 * 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
 */
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

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

  // Transform request data
  config.data = transformData.call(
    config,
    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];
    }
  );

  ...
  
};

第一部分主要是处理请求头及 data,比如将请求头拍扁合并、删除掉一些请求头等。

这里面有一个 transformData 方法,该方法位于 /lib/core/transformData.js,主要是调用 config.transformRequest 中传入的方法在请求前对请求中的 data 以及 headers 进行处理。

根据当前环境或配置获取请求适配器

/**
 * 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
 */
module.exports = function dispatchRequest(config) {
  ...

  var adapter = config.adapter || defaults.adapter;

  ...
};

第二部分是获取当前环境能够使用的请求适配器。此适配器可以通过 config 配置进行传入,也可以使用 axios 默认的 defaults.adapter 来获取请求。

如果使用传入的 adapter, 则需要确保能够解析 axios 的配置信息。

如果使用默认的设置,则会判断当前环境是什么环境。 defaults.adapter 实质是一个环境判断加载函数,位于 lib/defaults/index.js, 这个方法我们曾经在第二章里面提到过。

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

请求后响应处理

请求作为一个 promise执行,分别对请求后的正常及异常进行处理。

/**
 * 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
 */
module.exports = function dispatchRequest(config) {
  ...

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

    // Transform response data
    response.data = transformData.call(
      config,
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

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

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

正常和异常结果的处理流程都相差无几,主要是判断是否是取消请求了再使用 transformData 通过调用 config.transformResponse 来处理响应,后再返回响应。

transformData

/**
 * Transform the data for a request or a response
 *
 * @param {Object|String} data The data to be transformed
 * @param {Array} headers The headers for the request or response
 * @param {Array|Function} fns A single function or Array of functions
 * @returns {*} The resulting transformed data
 */
module.exports = function transformData(data, headers, fns) {
  var context = this || defaults;
  /*eslint no-param-reassign:0*/
  utils.forEach(fns, function transform(fn) {
    data = fn.call(context, data, headers);
  });

  return data;
};

transformData 位于 /lib/core/transformData.js。这是一个请求/响应预处理的方法。

这个方法有三个参数, data 是最终结果集,headers 是请求/响应头,fns 则是 config.transformResponseconfig.transformResponse 的传入参数,类型为函数数组。

这个方法调用一般是使用 call() 进行调用,在第一个参数中传入 config ,则 this 就变成了传入的 config

大致步骤就是将传入配置或默认配置作为上下文,调用通过 config.transformResponseconfig.transformResponse 的传入参数函数对上下文、请求/响应体、请求/响应头进行处理,并将处理后的结果作为新的请求/响应体返回。

需要注意的是,调用回调函数使用的也是 call ,所以 context 上下文是作为函数中的 this 出现的。