从零实现axios(1.1小节-创建Axios类)

96 阅读3分钟

axios 从何而来?

我们知道可以通过require('axios').default;的方式来加载 axios,该实例是通过createInstance函数所创建,代码如下:

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  // 这里先省去其他函数对context的扩展
  return context;
}
var axios = createInstance(defaults);

创建 Axios 类

通过上面代码,我们知道Axios是核心类,正是通过该类来构造axios 实例,然后我们就可以通过axios.get()等方式来进行 http 请求。

我们先创建一个 axios 文件夹,该文件夹作为项目根目录,然后在根目下创建 core 文件夹,在 core 文件夹里面创建 Axios.js 文件。

Axios类接收一个instanceConfig参数,该参数为axios实例的默认配置对象,该默认对象用来设置请求参数,比如请求超时时间等,我们之后会详细介绍这个对象。

我们在Axios的原型上添加一个request方法,axios中所有的http请求,比如GetPost请求都是对该方法的封装调用,所有该方法非常重要。request方法接收一个自定义的配置对象,在该配置对象中可以定义请求的 url、请求 method 等内容;该方法也可以接收一个字符串,该字符串代表的是请求 url。

var dispatchRequest = require("./dispatchRequest");

/**
 * axios实例构造函数
 *
 * @param {Object} instanceConfig 实例配置对象
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
}

// 参数为url字符串: axios.request('/user/12345')
// 参数为对象:axios.request({ method: 'post', url: '/user/12345', data: { a: 'a'}})
Axios.prototype.request = function request(configOrUrl, config) {
  // 这里是为了把config统一成一种数据格式
  if (typeof configOrUrl === "string") {
    // config是字符串则表明config参数是请求url
    // 我们可通过axios('/url', config?)的方式来调用request方法
    // 因此第二个参数可以是一个config对象,也可以不存在
    config = config || {};
    config.url = configOrUrl;
  } else {
    config = config || {};
  }

  // 设置http请求的类型, 优先使用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";
  }

  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  while (chain.length) {
    // 相当于 promise = Promise.resolve(config).then(dispatchRequest, undefined)
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

根据上面的代码,可知实际请求是在dispatchRequest方法中发出的,我们接着实现该函数

我们在 core 文件夹下创建 dispatchRequest.js 文件,该函数接收一个配置对象(即 Promise.resolve(config))。dispatchRequest 函数通过调用adapter方法发起 http 请求,该方法可以通过 config 对象进行自定义,如果没有自定义,则从默认配置文件中获取

dispatchRequest 函数返回值为 promise, 这样子我们就可以在请求外面异步处理响应数据

// 发出一个请求
const promise = axios.request("/url");

promise.then(
  (res) => {
    // res为onAdapterResolution回调的response返回值
    // 处理请求成功
  },
  (err) => {
    // err为onAdapterResolution回调的reason返回值
    // 处理请求失败
  }
);

上面的代码就是我们平时在工作中的实际用法,这就是通过 promise 来处理异步的强大之处,下面的代码为 dispatchRequest 函数的实现

var defaults = require("../defaults");

module.exports = function dispatchRequest(config) {
  // 如果config中不包含adapter,那就用默认的adapter(适配器)
  var adapter = config.adapter || defaults.adapter;

  // 在adapter函数中发起请求
  // onAdapterResolution回调接收请求成功后的数据
  // onAdapterRejection回调接收请求失败后的数据
  // adapter函数返回一个promise, 这些数据可以被request函数中的promise捕获
  return adapter(config).then(
    function onAdapterResolution(response) {
      return response;
    },
    function onAdapterRejection(reason) {
      return Promise.reject(reason);
    }
  );
};

我们在根目录下创建 defaults 目录,该目录下通过index.js导出一个默认配置对象,目前该对象只包含 adapter 方法

// 获取浏览器或node环境下发http请求的adapter
// 我们只讲浏览器部分
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== "undefined") {
    // 用于发起浏览器xhr请求
    adapter = require("../adapters/xhr");
  }
  return adapter;
}

var defaults = {
  adapter: getDefaultAdapter(),
};

module.exports = defaults;

我们在下一节实现adapter的内容