Объясните исходный код axios простым языком

JavaScript axios

предисловие

axios в настоящее время является наиболее часто используемой библиотекой HTTP-запросов, которую можно использовать в браузерах и node.js, и она имеет около 43 тысяч звезд на github.

Ключевые особенности Axios включают в себя:

  • Обещание на основе
  • Поддержка браузера и node.js
  • Может добавлять перехватчики и преобразовывать данные запросов и ответов.
  • Запрос может быть отменен
  • Автоматически преобразовывать данные JSON
  • Поддержка клиентов против XSRF
  • Поддержка всех основных браузеров и IE8+

В этой статье вы познакомитесь с исходным кодом axios, проанализируете некоторые навыки упаковки, реализацию конкретных функций и некоторые идеи для чтения исходного кода.

Строительство окружающей среды

Чтение исходного кода — это не просто «чтение», много раз перед лицом сложных контекстных зависимостей и ввода и вывода, это часто похоже на беспорядок. В это время часто лучше добавить несколько журналов, чем представлять это из ничего. И вы можете игнорировать некоторые менее важные ссылки, чтобы было легче поймать магистраль.

Если вы хотите сделать хорошую работу, вы должны сначала использовать острые инструменты.Нам нужно создать игровую площадку, чтобы наблюдать за изменениями и наблюдать за выводом нашего журнала. Некоторые примеры уже включены в axios, но инструмент сборки проекта основан на grunt и смены часов нет, нам нужно добавить его самим.
существует./GruntFile.jsдобавлено вgrunt.loadNpmTasks('grunt-contrib-watch');Вот и все. Или мы можем использовать инструменты сборки, чтобы сделать это. затем начните примеры Задач по сборке сервера и часов достаточно.

npm run examples

npm run dev

# open localhost:3000

Структура каталогов проекта

├── /lib/                      # 项目源码目
│ ├── /adapters/               # 定义发送请求的适配器
│ │ ├── http.js                # node环境http对象
│ │ └── xhr.js                 # 浏览器环境XML对象
│ ├── /cancel/                 # 定义取消功能
│ ├── /helpers/                # 一些辅助方法
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios实例构造函数
│ │ ├── createError.js         # 抛出错误
│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js  # 拦截器管理器
│ │ ├── mergeConfig.js         # 合并参数
│ │ ├── settle.js              # 根据http响应状态,改变Promise的状态
│ │ └── transformData.js       # 改变数据格式
│ ├── axios.js                 # 入口,创建构造函数
│ ├── defaults.js              # 默认配置
│ └── utils.js                 # 公用工具

Начните с API

При анализе исходного кода нам нужно начать с API, попытаться угадать внутреннюю структуру, а затем посмотреть на исходный код с проблемой будет более эффективно. Давайте примерно классифицируем API аксиом:

Классификация API axios

Выше приведена примерно общая классификация API в axios. Мы видим, что экземпляры, созданные под выставленным нам объектом axios, наследуются, кроме всех и распространения и методов отмены запроса, которые можно использовать независимо и поддерживать свой собственный контекст. Давайте начнем с записи и посмотрим, как она реализована:

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

/**
 * 创建Axios实例
 */
function createInstance(defaultConfig) {
  // new Axios 得到一个上下文环境 包含defatults配置以及拦截器
  var context = new Axios(defaultConfig);

  // instance实例为bind返回的一个函数(即是request发送请求方法),此时this绑定到context上下文环境
  var instance = bind(Axios.prototype.request, context);
  // 将Axios构造函数中的原型方法绑定到instance上并且指定this作用域为context上下文环境
  utils.extend(instance, Axios.prototype, context);
  // 把上下文环境中的defaults 以及拦截器绑定到instance实例中
  utils.extend(instance, context);

  return instance;
}

// axios入口其实就是一个创建好的实例
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");

// all 和 spread 两个处理并行的静态方法
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require("./helpers/spread");

module.exports = axios;

// 允许使用Ts 中的 default import 语法
module.exports.default = axios;

Анализируя вышеприведенную запись, мы можем увидеть подсказку: запись axios на самом деле является тем же экземпляром, созданным createInstance, что и экземпляр, созданный axios.create(). В центре записи исходного кода находится метод createInstance. Процесс createInstance примерно выглядит следующим образом:

  1. Используйте функции Axios для создания контекстного контекста, содержащего собственную конфигурацию по умолчанию и массив перехватчиков управления.
  2. Используйте Axios.prototype.request и контекст для создания экземпляра экземпляра, экземпляр — это функция, которая отправляет запрос запроса, который указывает на контекст контекста.
  3. Привяжите другие методы Axios.prototype к экземпляру экземпляра, это указывает на контекст контекста
  4. Привязать значения по умолчанию и перехватчики в контексте к экземпляру экземпляра

запросить псевдоним

В axios псевдонимы методов запроса, такие как axios.get, axios.delete и axios.head, фактически указывают на один и тот же метод, Axios.request просто изменяет методы запроса в конфигурации по умолчанию. Конкретный код находится на прототипе конструктора Axios. Давайте посмотрим на реализацию исходного кода:

utils.forEach(
  ["delete", "get", "head", "options"],
  function forEachMethodNoData(method) {
    Axios.prototype[method] = function(url, config) {
      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
      })
    );
  };
});

Поскольку post , put и patch имеют тела запросов, их следует обрабатывать отдельно. Псевдонимы запросов позволяют пользователям быстро делать запросы, используя различные API.

Реализация перехватчика

Прежде всего, когда мы создаем экземпляр, мы создадим экземпляр контекста, то есть новый Axios, и мы получим атрибут перехватчиков.У этого атрибута есть два атрибута, запрос и ответ.Их значения представляют собой массивы, возвращаемые новый конструктор InterceptorManager. Этот конструктор также отвечает за добавление и удаление массива перехватчиков. Давайте посмотрим на исходный код:

"use strict";

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

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

// axio或实例上调用 interceptors.request.use 或者 interceptors.resopnse.use
// 传入的resolve, reject 将被添加入数组尾部
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
// 移除拦截器,将该项在数组中置成null
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 辅助方法,帮助便利拦截器数组,跳过被eject置成null的项
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

Контекстная среда имеет массив перехватчиков, как добиться последовательной обработки и реализации нескольких запросов перехватчиков к ответам? Чтобы понять это, нам нужно глубже изучить метод Axios.protoType.request.

Axios.protoType.request

Метод Axios.protoType.request является точкой входа для старта запроса, он обрабатывает конфиг запроса, и цепочку обработки перехватчиков запросов, перехватчиков запросов и ответов, и возвращает Proimse Формат удобный нам для обработки колбэков. Давайте посмотрим на часть исходного кода:

Axios.prototype.request = function request(config) {
  //判断参数类型,支持axios('url',{})以及axios(config)两种形式
  if (typeof config === "string") {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }
  //传入参数与axios或实例下的defaults属性合并
  config = mergeConfig(this.defaults, config);
  config.method = config.method ? config.method.toLowerCase() : "get";

  // 创造一个请求序列数组,第一位是发送请求的方法,第二位是空
  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);
  });
  //遍历请求序列数组形成prmise链依次处理并且处理完弹出请求序列数组
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  //返回最终promise对象
  return promise;
};

Мы видим, что Axios.protoType.request использует тонкий метод инкапсуляции для формирования цепочки обещаний, которая будет смонтирована в методе then для последовательной обработки. Для более ясного понимания мы можем нарисовать схему, чтобы облегчить понимание этого процесса.

Массив последовательностей запросов перехватчика

отменить запрос

Axios.prototype.request вызывает dispatchRequest — это функция, которая, наконец, обрабатывает запрос, инициированный axios.Его процесс выполнения включает в себя:

  1. Обработка и рассмотрение запросов на отмену
  2. Обработка параметров и параметров по умолчанию
  3. Используйте соответствующий адаптер среды для отправки запроса (среда браузера использует объект XMLRequest, Node использует объект http).
  4. После возврата выдается сообщение с запросом на отмену, и данные ответа преобразуются в соответствии с конфигурацией transformData.

Этот процесс относительно прост в дополнение к отмене запроса, а остальные процессы относительно просты, поэтому мы должны проанализировать запрос на отмену. Мы по-прежнему смотрим на модифицированный способ:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios
  .get("/user/12345", {
    cancelToken: source.token
  })
  .catch(function(thrown) {
    if (axios.isCancel(thrown)) {
      console.log("Request canceled", thrown.message);
    } else {
      // handle error
    }
  });

source.cancel("Operation canceled by the user.");

Из метода вызова мы видим, что нам нужно передать axios.CancelToken.source().token из конфигурации, и мы можем использовать axios.CancelToken.source().cancel() для выполнения запроса на отмену. Мы также можем видеть, что функция canel не только отменяет запрос, но и переводит весь запрос в состояние reject. Из всего дизайна API мы видим, что функция этой части может быть немного сложной, давайте немного проанализируем ее и посмотрим на процесс реализации из метода CancelToken.source:

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

axios.CancelToken.source().token возвращает экземпляр нового CancelToken, axios.CancelToken.source().cancel, если новый CancelToken является параметром метода, переданного в новый CancelToken. Взгляните на конструктор CancelToken:

function CancelToken(executor) {
  if (typeof executor !== "function") {
    throw new TypeError("executor must be a function.");
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

По конструктору мы можем знать, что экземпляр, полученный с помощью axios.CancelToken.source().token, имеет два прикрепленных к нему атрибута: обещание и причина.Атрибут обещания — это экземпляр обещания в состоянии ожидания, а причина передается после выполнения метода отмены. А axios.CancelToken.source().cancel — это метод функции, отвечающий за решение о том, следует ли выполнять, если нет, то получить axios.CancelToken.source().token.promise Параметр разрешения исполнителя используется в качестве триггера для запуска обещания в состоянии ожидания, а входящее сообщение монтируется в xios.CancelToken.source().token.reason. Если какой-либо из них был смонтирован по уважительной причине, он будет возвращен во избежание повторного срабатывания. И как это отложенное обещание входит в отказ от общего обещания аксиом после отмены. Нам нужно посмотреть на обработку в адаптере:

//如果有cancelToken
if (config.cancelToken) {
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
      return;
    }
    //取消请求
    request.abort();
    //axios的promise进入rejected
    reject(cancel);
    // 清楚request请求对象
    request = null;
  });
}

Общая логика запроса на отмену в общем-то такая, что может быть сложно для понимания.Чтобы прочувствовать внутренний процесс, нужно несколько раз посмотреть исходный код.Примерно повторим общий процесс:

  1. axios.CancelToken.source() возвращает объект, свойство token является экземпляром класса CancelToken, отмена — это триггер разрешения обещания внутри токенов.
  2. Конфигурация axios принимает экземпляр класса CancelToken
  3. Когда отмена запускает ожидание tokens.promise, отмените запрос и переведите обещание аксиом в отклоненное состояние.

Суммировать

Общий процесс аксиом обычно представлен выше, мы можем нарисовать картинку, чтобы разобраться в ней более интуитивно.

процесс запроса аксиом

Анализ исходного кода Axios находится здесь, если есть ошибка, сообщите