Анализ исходного кода Axios

JavaScript axios

Что такое Аксиос?

Axios — это HTTP-библиотека на основе обещаний, которую можно использовать в браузерах и node.js. Сегодня мы приступим к анализу исходного кода Axios.

Особенности Аксиос

  • Создать из браузераXMLHttpRequests
  • Создано из node.jshttpпросить
  • служба поддержкиPromise API
  • Перехват запросов и ответов
  • Преобразование данных запроса и данных ответа
  • отменить запрос
  • Автоматически преобразовывать данные JSON
  • Поддержка клиентов против XSRF

Я надеюсь, что принцип реализации этих функций можно будет постепенно прояснить через исходный код

Аксиос использовать

воплощать в жизньGETпросить

axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })

воплощать в жизнь POST просить

axios.post('/user', {
    name: 'zxm',
    age: 18,
  })
  .then(function (response) {
    console.log(response);
  })

Метод использования не находится в центре внимания этой темы, конкретный метод использования может относиться кИнструкции Axios на китайском языке

Вытащите исходный код и напрямую войдите в папку lib, чтобы начать читать исходный код.

Интерпретация исходного кода

lib/axios.js запускается

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

// 重点 createInstance 方法
// 先眼熟一个代码 下面讲完工具函数会再具体来讲解 createInstance
function createInstance(defaultConfig) {
    // 实例化 Axios
  var context = new Axios(defaultConfig);
    // 自定义 bind 方法 返回一个函数()=> {Axios.prototype.request.apply(context,args)}
  var instance = bind(Axios.prototype.request, context);
    // Axios 源码的工具类 
  utils.extend(instance, Axios.prototype, context);
    
  utils.extend(instance, context);
    
  return instance;
}
// 传入一个默认配置   defaults 配置先不管,后面会有具体的细节
var axios = createInstance(defaults);


// 下面都是为 axios 实例化的对象增加不同的方法。
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;

Методы утилиты lib / util.js

Существуют следующие методы:

module.exports = {
  isArray: isArray,
  isArrayBuffer: isArrayBuffer,
  isBuffer: isBuffer,
  isFormData: isFormData,
  isArrayBufferView: isArrayBufferView,
  isString: isString,
  isNumber: isNumber,
  isObject: isObject,
  isUndefined: isUndefined,
  isDate: isDate,
  isFile: isFile,
  isBlob: isBlob,
  isFunction: isFunction,
  isStream: isStream,
  isURLSearchParams: isURLSearchParams,
  isStandardBrowserEnv: isStandardBrowserEnv,
  forEach: forEach,
  merge: merge,
  deepMerge: deepMerge,
  extend: extend,
  trim: trim
};

Имена методов isXxx в начале is позволяют судить о том,XxxТипа, не буду тут объяснять, в основном смотреть на следующие способы

extend наследует свойства и методы в b от a и привязывает контекст выполнения методов в b к thisArg

// a, b,thisArg 参数都为一个对象
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
      // 如果指定了 thisArg 那么绑定执行上下文到 thisArg
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

Давайте рассмотрим пример в аннотации

Это очевидно с первого взгляда. Функция fn2 не принимает возраст = 20 в своем собственном объекте, но присваивается возрасту в thisArg.

Пользовательский метод 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);
      }
    }
  }
}

слияние объединяет атрибуты объекта, тот же атрибут после замены до замены

function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  function assignValue(val, key) {
    if (typeof result[key] === 'object' && typeof val === 'object') {
      result[key] = merge(result[key], val);
    } else {
      result[key] = val;
    }
  }

  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}

Как показано ниже:

bind -> lib/ helpers/ bind.js Это очень понятно, возвращает функцию, а контекст выполнения входящего метода привязан к thisArg.

module.exports = function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};

Что ж, тогда у нас практически нет проблем с методом axios/util.

После прочтения этих методов служебного класса давайте вернемся к предыдущему методу createInstance.

function createInstance(defaultConfig) {
    // 实例化 Axios, Axios下面会讲到
  var context = new Axios(defaultConfig);
    
    // 将 Axios.prototype.request 的执行上下文绑定到 context
    // bind 方法返回的是一个函数
  var instance = bind(Axios.prototype.request, context);
    
    // 将 Axios.prototype 上的所有方法的执行上下文绑定到 context , 并且继承给 instance
  utils.extend(instance, Axios.prototype, context);
    
    // 将 context 继承给 instance
  utils.extend(instance, context);
    
  return instance;
}
// 传入一个默认配置  
var axios = createInstance(defaults);

Суммировать:createInstanceФункция возвращает экземпляр функции.

  1. instance — это функция Axios.prototype.request, и контекст выполнения привязан к контексту.
  2. Также в экземпляре есть все методы выше Axios.prototype, и контекст выполнения этих методов также привязан к контексту.
  3. Существуют также методы для контекста в экземпляре.

Исходный код экземпляра Axios

'use strict';
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');

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

// 核心方法 request 
Axios.prototype.request = function request(config) {
  // ... 单独讲
};

// 合并配置将用户的配置 和默认的配置合并
Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// 这个就是给 Axios.prototype 上面增加 delete,get,head,options 方法
// 这样我们就可以使用 axios.get(), axios.post() 等等方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
     // 都是调用了 this.request 方法
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

Все вышеперечисленные методы вызываются вызовом метода this.request

Итак, давайте взглянем на метод запроса.Я лично думаю, что это суть исходного кода, и это также сложная для понимания часть.Он использует цепной вызов Promise и идею промежуточного программного обеспечения.

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Axios.prototype.request = function request(config) {
  // 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);
    // 请求方式,没有默认为 get
  config.method = config.method ? config.method.toLowerCase() : 'get';
    
    // 重点 这个就是拦截器的中间件
  var chain = [dispatchRequest, undefined];
    // 生成一个 promise 对象
  var promise = Promise.resolve(config);

    // 将请求前方法置入 chain 数组的前面 一次置入两个 成功的,失败的
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
	// 将请求后的方法置入 chain 数组的后面 一次置入两个 成功的,失败的
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

   // 通过 shift 方法把第一个元素从其中删除,并返回第一个元素。
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Видеть здесь немного абстрактно, это нормально. Сначала поговорим о перехватчиках. в запросе или ответеthen или catchПерехватите их перед обработкой. Использовать ссылку на методИнструкции Axios на китайском языке, примерно используется следующим образом.

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

Вызов функции через цепочку промисов, эта функция является методом в массиве цепочек

// 初始的 chain 数组 dispatchRequest 是发送请求的方法
var chain = [dispatchRequest, undefined];

// 然后 遍历 interceptors 
// 注意 这里的 forEach 不是 Array.forEach, 也不是上面讲到的 util.forEach. 具体 拦截器源码 会讲到
// 现在我们只要理解他是遍历给 chain 里面追加两个方法就可以
  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);
  });

Затем добавьте перехватчик запроса и соответствующую цепочку перехватчиков. Как это будет выглядеть (выделение)

chain = [ 请求拦截器的成功方法,请求拦截器的失败方法,dispatchRequest, undefined, 响应拦截器的成功方法,响应拦截器的失败方法 ]。

Ну, как это выглядит, зная конкретное использование? Вернитесь и посмотрите на метод запроса

На каждый запрос у нас есть

 while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
   意思就是将 chainn 内的方法两两拿出来执行 成如下这样
    promise.then(请求拦截器的成功方法, 请求拦截器的失败方法)
           .then(dispatchRequest, undefined)
           .then(响应拦截器的成功方法, 响应拦截器的失败方法)

Теперь посмотрим, если стало намного понятнее, принцип работы перехватчика. Теперь давайте посмотрим на исходный код InterceptorManager.

Исходный код перехватчика InterceptorManager

lib/ core/ InterceptorManager.js

'use strict';
var utils = require('./../utils');

function InterceptorManager() {
    // 存放方法的数组
  this.handlers = [];
}
// 通过 use 方法来添加拦截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
// 通过 eject 方法来删除拦截方法
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};
// 添加一个 forEach 方法,这就是上述说的 forEach
InterceptorManager.prototype.forEach = function forEach(fn) {
    // 里面还是依旧使用了 utils 的 forEach, 不要纠结这些 forEach 的具体代码
    // 明白他们干了什么就可以
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

исходный код запроса отправки

lib/ core/ dispatchRequest .js

'use strict';
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');
// 请求取消时候的方法,暂不看
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);
    // 请求没有取消 执行下面的请求
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }
  config.headers = config.headers || {};
	// 转换数据
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
    // 合并配置
  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];
    }
  );
    // 这里是重点, 获取请求的方式,下面会将到
  var adapter = config.adapter || defaults.adapter;
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
	// 难道了请求的数据, 转换 data
    response.data = transformData(
      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(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

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

Прочитав так много, мы не увидели, что используется для отправки запроса, теперь давайте посмотрим, что мы передали в методе createInstance начального экземпляра.defaultsчто

var axios = createInstance(defaults);

lib/ defaults.js

'use strict';

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}
// getDefaultAdapter 方法是来获取请求的方式
function getDefaultAdapter() {
  var adapter;
  // process 是 node 环境的全局变量
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // 如果是 node 环境那么久通过 node http 的请求方法
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
   // 如果是浏览器啥的 有 XMLHttpRequest 的就用 XMLHttpRequest
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

var defaults = {
    // adapter 就是请求的方法
  adapter: getDefaultAdapter(),
	// 下面一些请求头,转换数据,请求,详情的数据
    // 这也就是为什么我们可以直接拿到请求的数据时一个对象,如果用 ajax 我们拿到的都是 jSON 格式的字符串
    // 然后每次都通过 JSON.stringify(data)来处理结果。
  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,

  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;

Суммировать

  1. Вы действительно можете многое увидеть и узнать, прочитав исходный код Axios.
  2. У Axios также есть некоторые функции: отмена запросов, обработка тайм-аутов запросов. У меня нет всех инструкций здесь.
  3. Axios обеспечивает поддержку XSRF на стороне клиента, добавляя в запрос токен и метод проверки.Принципиальный анализ Django CSRF

Наконец

Если вы все еще этого не понимаете, не волнуйтесь, это в основном мое выражение, и письмо недостаточно хорошо. Потому что, когда писал статью, я ее тоже неоднократно удалял и переписывал, но всегда чувствовал, что выражение недостаточно ясное. Чтобы укрепить понимание и обучение, вы можете перейти на github, чтобы получить код и сравнить его. Это сделает более понятным анализ исходного кода Axios.

git clone https://github.com/axios/axios.git

Во всей статье, если есть какие-либо ошибки или неточности, обязательно исправьте их, спасибо!

Ссылаться на: