анализ исходного кода axios - статьи XHR
Исходный код статьи размещен по адресуgithubДобро пожаловать на вилочную коррекцию!
axiosЭто библиотека HTTP-запросов на основе Promise, которую можно использовать в браузерах и node.js, и в настоящее время она имеет 42 000 звезд на github.
Примечание:
- Каждый раздел будет представлен с двух сторон: как использовать -> анализ исходного кода.
- Раздел [Краткое введение в инструменты и методы] можно сначала пропустить, а потом зайти проверить
- Основным техническим моментом axios являетсяКак перехватить ответ на запрос и изменить параметры запроса, изменить данные ответаиКак axios использует обещания для построения асинхронного моста на основе xhr
структура каталогов проекта axios
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主类
│ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js # 拦截器构造函数
│ │ └── settle.js # 根据http响应状态,改变Promise的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现http适配器
│ │ └── xhr.js # 实现xhr适配器
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # 默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件
Примечание. Поскольку код, на который нам нужно обратить внимание,/lib/
Файлы в каталоге, поэтому все следующие места относятся к пути к файлу,
мы все будем/lib/
ищите ниже
Глоссарий
-
перехватчики
(Если вы знакомы с промежуточным программным обеспечением, то его легко понять, потому что оно играет роль промежуточного программного обеспечения на основе обещаний)
Перехватчики делятся на перехватчики запросов и перехватчики ответов, как следует из названия: перехватчик запросов (
interceptors.request
) означает, что вы можете перехватывать каждый или указывать http-запрос и изменять элементы конфигурации перехватчик ответа (interceptors.response
) может перехватывать каждый или указанный http-запрос после каждого http-запроса и может изменять возвращаемый элемент результата.Вот краткое введение, а подробное введение будет дано позже.Как перехватить ответ на запрос и изменить параметры запроса, изменить данные ответа.
-
Преобразователь данных (фактически преобразует данные, например, преобразование объектов в строки JSON)
Преобразователи данных делятся на преобразователи запросов и преобразователи ответов, как следует из названия: конвертер запросов (
transformRequest
) относится к преобразованию данных перед запросом, преобразователь ответов (transformResponse
) основного тела в ответ на запрос ответа на преобразование данных. -
адаптер запроса http (фактически метод)
В проекте axios адаптеры http-запросов в основном относятся к двум типам: XHR и http. Ядром XHR является объект XMLHttpRequest на стороне браузера. Ядром http является метод http[s].request узла
Конечно, axios также оставляет пользователям интерфейс для самостоятельной настройки адаптера через config. Однако в целом эти два адаптера могут удовлетворить требования по отправке запросов из браузера на сервер или с http-клиента узла на сервер.
Этот обмен в основном вращается вокруг XHR.
-
элемент конфигурации config (фактически объект)
Конфиг, о котором мы здесь говорим, на самом деле не называется именем переменной config в проекте, это имя я сделал в соответствии с его использованием, чтобы все могли его понять.
В проекте axios при настройке\чтении конфига некоторые места называют это
defaults
(/lib/defaults.js
), здесь элемент конфигурации по умолчанию, некоторые места называют этоconfig
,какAxios.prototype.request
параметры, такие какxhrAdapter
Параметры метода адаптера http-запроса.config — это очень важная цепочка в проекте axios, и это главный мост между пользователем и внутренней «коммуникацией» проекта axios.
Блок-схема внутренней работы axios
Краткое введение в инструменты и методы
(Примечание: этот раздел можно сначала пропустить, а затем вернуться к проверке позже)
Есть некоторые методы, которые используются в нескольких местах проекта, кратко ознакомьтесь с этими методами.
- Bind: укажите контекст для функции, т. е. this указывает на
bind(fn, context);
добиться такого же эффектаFunction.prototype.bind
метод:fn.bind(context)
- forEach: перебирает массив или объект
var utils = require('./utils');
var forEach = utils.forEach;
// 数组
utils.forEach([], (value, index, array) => {})
// 对象
utils.forEach({}, (value, key, object) => {})
- слияние: глубокое слияние нескольких объектов в один объект
var utils = require('./utils');
var merge = utils.merge;
var obj1 = {
a: 1,
b: {
bb: 11,
bbb: 111,
}
};
var obj2 = {
a: 2,
b: {
bb: 22,
}
};
var mergedObj = merge(obj1, obj2);
Объект mergedObj:
{
a: 2,
b: {
bb: 22,
bbb: 111
}
}
- расширить: расширить методы и свойства одного объекта на другой объект и указать контекст
var utils = require('./utils');
var extend = utils.extend;
var context = {
a: 4,
};
var target = {
k: 'k1',
fn(){
console.log(this.a + 1)
}
};
var source = {
k: 'k2',
fn(){
console.log(this.a - 1)
}
};
let extendObj = extend(target, source, context);
Объект extendObj:
{
k: 'k2',
fn: source.fn.bind(context),
}
воплощать в жизньextendObj.fn();
, Распечатать3
Почему аксиомы можно использовать по-разному
как пользоваться
// 首先将axios包引进来
import axios from 'axios'
Первый способ использования:axios(option)
axios({
url,
method,
headers,
})
Второй способ использования:axios(url[, option])
axios(url, {
method,
headers,
})
Третий способ использования (дляget、delete
и т.д.):axios[method](url[, option])
axios.get(url, {
headers,
})
4-е использование (дляpost、put
и т.д.):axios[method](url[, data[, option]])
axios.post(url, data, {
headers,
})
5-й способ использования:axios.request(option)
axios.request({
url,
method,
headers,
})
Анализ исходного кода
В качестве входного файла проекта axios давайте сначала посмотримaxios.js
исходный код
Ядром многократного использования axios являетсяcreateInstance
метод:
// /lib/axios.js
function createInstance(defaultConfig) {
// 创建一个Axios实例
var context = new Axios(defaultConfig);
// 以下代码也可以这样实现:var instance = Axios.prototype.request.bind(context);
// 这样instance就指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用
// Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
var instance = bind(Axios.prototype.request, context);
// 把Axios.prototype上的方法扩展到instance对象上,
// 这样 instance 就有了 get、post、put等方法
// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
utils.extend(instance, Axios.prototype, context);
// 把context对象上的自身属性和方法扩展到instance上
// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
// 这样,instance 就有了 defaults、interceptors 属性。(这两个属性后面我们会介绍)
utils.extend(instance, context);
return instance;
}
// 接收默认配置项作为参数(后面会介绍配置项),创建一个Axios实例,最终会被作为对象导出
var axios = createInstance(defaults);
Приведенный выше код выглядит запутанным, но на самом делеcreateInstance
В итоге я надеюсь получить функцию, которая указывает наAxios.prototype.request
, эта Функция также будет иметьAxios.prototype
Каждый метод является статическим методом, и контекст всех этих методов указывает на один и тот же объект.
Тогда приходи и смотриAxios、Axios.prototype.request
Как выглядит исходный код?
Axios
является ядром пакета axios,Axios
Экземпляр представляет собой приложение axios, другие методы верныAxios
Расширение контента
иAxios
Основной метод конструктораrequest
метода, методы вызова различных аксиом в конечном счетеrequest
способ запроса
// /lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) {
// ...省略代码
};
// 为支持的请求方法提供别名
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
}));
};
});
С помощью приведенного выше кода мы можем инициировать http-запросы несколькими способами:axios()、axios.get()、axios.post()
В общем, проект может удовлетворить потребности, используя экспортированный экземпляр axios по умолчанию. Если необходимо создать новый экземпляр axios, если требования не выполняются, пакет axios также резервирует интерфейс. См. код ниже:
// /lib/axios.js - 31行
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
return createInstance(utils.merge(defaults, instanceConfig));
};
После разговора о том, почему существует так много способов использования аксиом, у вас может возникнуть вопрос:
При использовании axios неважноget
метод ещеpost
метод, который в конечном итоге называетсяAxios.prototype.request
метод, то как этот метод отправляет запросы на основе нашей конфигурации конфигурации?
в начале сказатьAxios.prototype.request
Прежде давайте взглянем на то, как настроенный пользователем конфиг работает в проекте axios?
Как работает пользовательская конфигурация?
сказал здесьconfig
, относится к объекту элемента конфигурации, который проходит через весь проект,
С помощью этого объекта вы можете установить:
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
Можно обнаружить, что почтиaxios
Все функции настраиваются и передаются через этот объект,
обаaxios
Коммуникационный мост внутри проекта также является коммуникационным мостом между пользователями и пользователями.axios
мост для связи.
Во-первых, давайте посмотрим, как пользователи могут определять элементы конфигурации:
import axios from 'axios'
// 第1种:直接修改Axios实例上defaults属性,主要用来设置通用配置
axios.defaults[configName] = value;
// 第2种:发起请求时最终会调用Axios.prototype.request方法,然后传入配置项,主要用来设置“个例”配置
axios({
url,
method,
headers,
})
// 第3种:新建一个Axios实例,传入配置项,此处设置的是通用配置
let newAxiosInstance = axios.create({
[configName]: value,
})
посмотриAxios.prototype.request
Строка кода в методе: (/lib/core/Axios.js
- строка 35)
config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
Вы можете найти объект конфигурации по умолчанию здесьdefaults
(/lib/defaults.js
), свойства экземпляра Axiosthis.defaults
,request
параметры запросаconfig
объединены.
Исходя из этого, приоритет нескольких конфигураций от низкого к высокому:
—> Объект конфигурации по умолчаниюdefaults
(/lib/defaults.js
)
--> { метод: 'получить' }
—> Свойства экземпляра Axiosthis.defaults
—>request
параметры запросаconfig
Дайте подумать над вопросом:defaults
иthis.defaults
Когда конфигурация одинакова, а когда отличается?
На данный момент мы получили рядmerge
Послеconfig
объект, то как этот объект передается в проект?
Axios.prototype.request = function request(config) {
// ...
config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
var chain = [dispatchRequest, undefined];
// 将config对象当作参数传给Primise.resolve方法
var promise = Promise.resolve(config);
// ...省略代码
while (chain.length) {
// config会按序通过 请求拦截器 - dispatchRequest方法 - 响应拦截器
// 关于拦截器 和 dispatchRequest方法,下面会作为一个专门的小节来介绍。
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Уже,config
Закончил свою легендарную жизнь-_-
Следующий раздел придет к основному событию:Axios.prototype.request
axios.prototype.request
Код здесь более сложный, и некоторые методы необходимо проследить до источника, чтобы понять это.
Так что просто имейте простое представление о массиве цепочек, который включает в себяперехватчик, [dispatchRequest
] будет подробно описано позже
chain
Массивы используются для хранения методов перехватчиков иdispatchRequest
метод,
через обещание отchain
Функции обратного вызова вынимаются по порядку из массива и выполняются одна за другой, и, наконец, обработанный новый промис помещается вAxios.prototype.request
выход из метода,
И отправить ответ или ошибку, этоAxios.prototype.request
миссия.
Посмотреть исходный код:
// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
// ...
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;
};
На данный момент вам должно быть любопытно узнать о перехватчике.Что за парень этот перехватчик?Давайте узнаем в следующем разделе.
Как перехватить ответ на запрос и изменить параметры запроса, изменить данные ответа
как пользоваться
// 添加请求拦截器
const myRequestInterceptor = axios.interceptors.request.use(config => {
// 在发送http请求之前做些什么
return config; // 有且必须有一个config对象被返回
}, error => {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(response => {
// 对响应数据做点什么
return response; // 有且必须有一个response对象被返回
}, error => {
// 对响应错误做点什么
return Promise.reject(error);
});
// 移除某次拦截器
axios.interceptors.request.eject(myRequestInterceptor);
считать
- Можно ли вернуть ошибку напрямую?
axios.interceptors.request.use(config => config, error => {
// 是否可以直接 return error ?
return Promise.reject(error);
});
- Как реализовать цепочку промисов
new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian');
// 打印结果
// (等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'
Анализ исходного кода
Что касается перехватчиков,ГлоссарийКраткое объяснение было дано в разделе.
Каждый экземпляр axios имеетinterceptors
свойства экземпляра,interceptors
У объекта есть два свойстваrequest
,response
.
function Axios(instanceConfig) {
// ...
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Оба свойства являютсяInterceptorManager
экземпляр, и этоInterceptorManager
Конструкторы используются для управления перехватчиками.
Давайте сначала посмотримInterceptorManager
Конструктор:
InterceptorManager
Конструктор используется для реализации перехватчиков, в прототипе этого конструктора есть три метода: use, eject и forEach.
Что касается исходного кода, то на самом деле он относительно прост и используется для работы с атрибутом экземпляра handlers конструктора.
// /lib/core/InterceptorManager.js
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;
}
};
// 遍历this.handlers,并将this.handlers里的每一项作为参数传给fn执行
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
тогда, когда мы проходимaxios.interceptors.request.use
После добавления перехватчика
Как axios позволяет этим перехватчикам получать нужные нам данные до и после запроса?
Сначала посмотрите на код:
// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
// ...
var chain = [dispatchRequest, undefined];
// 初始化一个promise对象,状态微resolved,接收到的参数微config对象
var promise = Promise.resolve(config);
// 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
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数组大概会是这样的:
// [
// requestFulfilledFn, requestRejectedFn, ...,
// dispatchRequest, undefined,
// responseFulfilledFn, responseRejectedFn, ....,
// ]
// 只要chain数组长度不为0,就一直执行while循环
while (chain.length) {
// 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
// 每次执行while循环,从chain数组里按序取出两项,并分别作为promise.then方法的第一个和第二个参数
// 按照我们使用InterceptorManager.prototype.use添加拦截器的规则,正好每次添加的就是我们通过InterceptorManager.prototype.use方法添加的成功和失败回调
// 通过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法,
// 对于请求拦截器,从拦截器数组按序读到后是通过unshift方法往chain数组数里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于请求拦截器,先添加的拦截器会后执行
// 对于响应拦截器,从拦截器数组按序读到后是通过push方法往chain数组里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于响应拦截器,添加的拦截器先执行
// 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,所以通过promise实现链式调用时,每个请求拦截器的fulfilled函数都会接收到一个config对象
// 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是我们的请求方法)请求到的数据(也就是response对象),而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,所以通过promise实现链式调用时,每个响应拦截器的fulfilled函数都会接收到一个response对象
// 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,所以dispatchRequest抛出的错误才会被响应拦截器接收到。
// 因为axios是通过promise实现的链式调用,所以我们可以在拦截器里进行异步操作,而拦截器的执行顺序还是会按照我们上面说的顺序执行,也就是 dispatchRequest 方法一定会等待所有的请求拦截器执行完后再开始执行,响应拦截器一定会等待 dispatchRequest 执行完后再开始执行。
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
К настоящему времени вы должны иметь четкое представление о том, что такое перехватчик и как работает перехватчик вAxios.prototype.request
Метод воспроизводится.
Так что в "среднем месте"dispatchRequest
Как отправляется http-запрос?
Что делает диспетчерский запрос
dispatchRequest в основном делает 3 вещи: 1. Получите объект конфигурации и выполните окончательную обработку перед передачей конфигурации адаптеру HTTP-запросов; 2. Адаптер HTTP-запроса инициирует запрос в соответствии с конфигурацией конфигурации. 3. После завершения запроса адаптера http-запроса, в случае его успешного выполнения, согласно заголовку, данным и config.transformResponse (о transformResponse следующеепреобразователь данныхОбъясню) получить ответ после преобразования данных, и вернуться.
// /lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// Support baseURL config
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
// Ensure headers exist
config.headers = config.headers || {};
// 对请求data进行转换
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 对header进行合并处理
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
// 删除header属性里无用的属性
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,不过大部分时候,axios提供的默认适配器是能够满足我们的
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(/**/);
};
Что ж, раз так, пора разобраться: как же axios использует промисы для построения асинхронного моста на основе xhr?
Как axios использует обещания для построения асинхронного моста на основе xhr
Как axios обрабатывает асинхронную обработку через промисы?
как пользоваться
import axios from 'axios'
axios.get(/**/)
.then(data => {
// 此处可以拿到向服务端请求回的数据
})
.catch(error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
})
Анализ исходного кода
Давайте сделаем простую картинку, чтобы понять поток последовательности пользователя после завершения http-запроса в проекте axios:
пройти черезПочему аксиомы можно использовать по-разномумы знаем,
Как бы пользователь ни называл axios, в конечном итоге он называетсяAxios.prototype.request
метод,
Этот метод в конечном итоге возвращает объект Promise.
Axios.prototype.request = function request(config) {
// ...
var chain = [dispatchRequest, undefined];
// 将config对象当作参数传给Primise.resolve方法
var promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Axios.prototype.request
метод вызоветdispatchRequest
метод, в то время какdispatchRequest
метод вызоветxhrAdapter
метод,xhrAdapter
Метод возвращает объект Promise
// /lib/adapters/xhr.js
function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// ... 省略代码
});
};
xhrAdapter
После того, как XHR внутри XHR успешно отправит запрос, объект Promise будет выполнен.resolve
способ и передать запрошенные данные,
В противном случае выполнитьreject
метод и передать сообщение об ошибке в качестве параметра.
// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';
request[loadEvent] = function handleLoad() {
// ...
// 往下走有settle的源码
settle(resolve, reject, response);
// ...
};
request.onerror = function handleError() {
reject(/**/);
request = null;
};
request.ontimeout = function handleTimeout() {
reject(/**/);
request = null;
};
Убедитесь, что результат, возвращаемый сервером, проходит проверку:
// /lib/core/settle.js
function settle(resolve, reject, response) {
var validateStatus = response.config.validateStatus;
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(/**/);
}
};
назадdispatchRequest
метод, сначала получитьxhrAdapter
Объект Promise, возвращаемый методом,
затем пройти.then
метод, даxhrAdapter
Результат успеха или неудачи возвращенного объекта Promise обрабатывается снова,
В случае успеха обрабатываетсяresponse
возвращение,
Если это не удается, он возвращает статусrejected
объект обещания,
return adapter(config).then(function onAdapterResolution(response) {
// ...
return response;
}, function onAdapterRejection(reason) {
// ...
return Promise.reject(reason);
});
};
Итак, в этот момент пользователь вызываетaxios()
метод, вы можете напрямую вызвать Promise.then
или.catch
Осуществляется бизнес-процесс.
Возвращаясь, мы представляемdispatchRequest
Преобразование данных, упомянутое в разделе, и официальное лицо axios также представило преобразование данных в качестве основного момента, так какой эффект может оказать преобразование данных при использовании axios?
Преобразователь данных - преобразует данные запроса и ответа
как пользоваться
- Изменить глобальный преобразователь
import axios from 'axios'
// 往现有的请求转换器里增加转换方法
axios.defaults.transformRequest.push((data, headers) => {
// ...处理data
return data;
});
// 重写请求转换器
axios.defaults.transformRequest = [(data, headers) => {
// ...处理data
return data;
}];
// 往现有的响应转换器里增加转换方法
axios.defaults.transformResponse.push((data, headers) => {
// ...处理data
return data;
});
// 重写响应转换器
axios.defaults.transformResponse = [(data, headers) => {
// ...处理data
return data;
}];
- Изменить преобразователь запроса axios
import axios from 'axios'
// 往已经存在的转换器里增加转换方法
axios.get(url, {
// ...
transformRequest: [
...axios.defaults.transformRequest, // 去掉这行代码就等于重写请求转换器了
(data, headers) => {
// ...处理data
return data;
}
],
transformResponse: [
...axios.defaults.transformResponse, // 去掉这行代码就等于重写响应转换器了
(data, headers) => {
// ...处理data
return data;
}
],
})
Анализ исходного кода
дефолтdefaults
Преобразователь запросов и преобразователь ответов были настроены в элементе конфигурации,
Посмотрите на исходный код:
// /lib/defaults.js
var defaults = {
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
// ...
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) {
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
};
Итак, в проекте axios где используется конвертер?
Использование преобразователя запросов заключается в использовании преобразователя запросов для обработки данных запроса перед HTTP-запросом. Затем передайте его адаптеру http-запроса для использования.
// /lib/core/dispatchRequest.js
function dispatchRequest(config) {
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
return adapter(config).then(/* ... */);
};
посмотриtransformData
код метода,
В основном просматривайте массив преобразователей, выполняйте каждый преобразователь отдельно и возвращайте новые данные в соответствии с параметрами данных и заголовков.
// /lib/core/transformData.js
function transformData(data, headers, fns) {
utils.forEach(fns, function transform(fn) {
data = fn(data, headers);
});
return data;
};
Использование преобразователя ответа заключается в выполнении обработки преобразования данных в соответствии с возвращаемым значением адаптера http-запроса после завершения http-запроса:
// /lib/core/dispatchRequest.js
return adapter(config).then(function onAdapterResolution(response) {
// ...
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
// ...
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
Связь между преобразователями и перехватчиками?
Перехватчики также могут реализовать требования преобразования данных запроса и ответа, но, согласно авторскому дизайну и всеобъемлющему коду, видно, что
При запросе перехватчик в основном отвечает за изменение элемента конфигурации конфигурации, а преобразователь данных в основном отвечает за преобразование тела запроса, например преобразование объекта в строку.
После ответа на запрос перехватчик может получитьresponse
, преобразователь данных в основном отвечает за обработку тела ответа, например за преобразование строки в объект.
Axios официально представляет «автоматическое преобразование в данные JSON» в качестве независимой особенности, так как же преобразователь данных выполняет эту функцию? На самом деле это очень просто, давайте посмотрим на это вместе.
Автоматически конвертировать данные json
По умолчанию axios автоматически сериализует входящий объект данных в строку JSON и преобразует строку JSON в данных ответа в объект JavaScript.
Анализ исходного кода
// 请求时,将data数据转换为JSON 字符串
// /lib/defaults.js
transformRequest: [function transformRequest(data, headers) {
// ...
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}]
// 得到响应后,将请求到的数据转换为JSON对象
// /lib/defaults.js
transformResponse: [function transformResponse(data) {
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}]
На данный момент внедрен процесс работы проекта axios.Вы вскрыли вторую жилу Рена и Ду? Далее давайте посмотрим, какие полезные очки умений принесли нам axios.
настройки заголовка
как пользоваться
import axios from 'axios'
// 设置通用header
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // xhr标识
// 设置某种请求的header
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
// 设置某次请求的header
axios.get(url, {
headers: {
'Authorization': 'whr1',
},
})
Анализ исходного кода
// /lib/core/dispatchRequest.js - 44行
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
Как отменить уже отправленный запрос
как пользоваться
import axios from 'axios'
// 第一种取消方法
axios.get(url, {
cancelToken: new axios.CancelToken(cancel => {
if (/* 取消条件 */) {
cancel('取消日志');
}
})
});
// 第二种取消方法
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(url, {
cancelToken: source.token
});
source.cancel('取消日志');
Анализ исходного кода
// /cancel/CancelToken.js - 11行
function CancelToken(executor) {
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);
});
}
// /lib/adapters/xhr.js - 159行
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
request = null;
});
}
Ядром функции отмены является CancelToken.this.promise = new Promise(resolve => resolvePromise = resolve)
,
получить свойства экземпляраpromise
, в это времяpromise
статусpending
Благодаря этому свойству в/lib/adapters/xhr.js
файл продолжает давать этоpromise
добавление экземпляра.then
метод
(xhr.js
строка 159 файлаconfig.cancelToken.promise.then(message => request.abort())
);
существуетCancelToken
снаружи, черезexecutor
получить правильные параметрыcancel
контроль метода,
поэтому при выполненииcancel
метод может изменить экземплярpromise
Состояние имуществаrejected
,
тем самым выполняяrequest.abort()
способ достижения цели отмены запроса.
Второй способ написания, описанный выше, можно рассматривать как совершенство первого способа письма. Поскольку многие из методов, которые мы используем для отмены запроса, используются вне этого метода запроса, Например, отправляются два запроса A и B, и когда запрос B выполняется успешно, запрос A отменяется.
// 第1种写法:
let source;
axios.get(Aurl, {
cancelToken: new axios.CancelToken(cancel => {
source = cancel;
})
});
axios.get(Burl)
.then(() => source('B请求成功了'));
// 第2种写法:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(Aurl, {
cancelToken: source.token
});
axios.get(Burl)
.then(() => source.cancel('B请求成功了'));
Условно говоря, я предпочитаю первый способ письма, потому что второй способ написания слишком скрытый и не такой интуитивный, как первый.
проблема обнаружена
-
В файле /lib/adapters/xhr.js аргумент метода oncanceled не должен называться message, почему Cancel?
-
В файле /lib/adapters/xhr.js в методе onCanceled информация о конфигурации также должна передаваться в reject
Перенос файлов cookie между доменами
как пользоваться
import axios from 'axios'
axios.defaults.withCredentials = true;
Анализ исходного кода
мы вКак работает пользовательская конфигурация?В разделе представлен процесс переноса конфига в проект axios,
Отсюда получаем поaxios.defaults.withCredentials = true
сделать настройку,
существует/lib/adapters/xhr.js
Его можно получить отсюда, а затем настроить для элемента объекта xhr с помощью следующего кода.
var request = new XMLHttpRequest();
// /lib/adapters/xhr.js
if (config.withCredentials) {
request.withCredentials = true;
}
Конфигурация тайм-аута и обработка
как пользоваться
import axios from 'axios'
axios.defaults.timeout = 3000;
Анализ исходного кода
// /adapters/xhr.js
request.timeout = config.timeout;
// /adapters/xhr.js
// 通过createError方法,将错误信息合为一个字符串
request.ontimeout = function handleTimeout() {
reject(createError('timeout of ' + config.timeout + 'ms exceeded',
config, 'ECONNABORTED', request));
};
- Как добавить обработку после таймаута вне библиотеки axios
axios().catch(error => {
const { message } = error;
if (message.indexOf('timeout') > -1){
// 超时处理
}
})
Переопределите правило validatestatus, которое проверяет успех или неудачу
Настройте диапазоны успехов и неудач кодов состояния http
как пользоваться
import axios from 'axios'
axios.defaults.validateStatus = status => status >= 200 && status < 300;
Анализ исходного кода
В конфигурации по умолчанию определены правила проверки кода состояния http по умолчанию,
так обычайvalidateStatus
По сути, это переписывание метода здесь
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
// ...
}
Когда axios начал проверять коды статуса http?
// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';
// /lib/adapters/xhr.js
// 每当 readyState 改变时,就会触发 onreadystatechange 事件
request[loadEvent] = function handleLoad() {
if (!request || (request.readyState !== 4 && !xDomain)) {
return;
}
// ...省略代码
var response = {
// ...
// IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
status: request.status === 1223 ? 204 : request.status,
config: config,
};
settle(resolve, reject, response);
// ...省略代码
}
// /lib/core/settle.js
function settle(resolve, reject, response) {
// 如果我们往上捣一捣就会发现,config对象的validateStatus就是我们自定义的validateStatus方法或默认的validateStatus方法
var validateStatus = response.config.validateStatus;
// validateStatus验证通过,就会触发resolve方法
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(createError(
'Request failed with status code ' + response.status,
response.config,
null,
response.request,
response
));
}
};
Суммировать
В проекте axios есть много умных способов использования JS, таких как последовательная работа промисов (конечно, можно также сказать, что это множество асинхронных методов обработки промежуточного ПО), чтобы мы могли легко обрабатывать различные процессы до и после запроса.Процесс этого метода обработки контролируется;множество практических мелких оптимизаций,таких как обработка данных до и после запроса,избавляют программиста от написания JSON.xxx снова и снова;в то же время он поддерживает оба среды браузера и узла, и подходит для проектов, использующих узел, Это, несомненно, превосходно.
Одним словом, эта звезда, которая может набрать 42K+ на github (по состоянию на 27.05.2018), ни в коем случае невозможна, и на нее стоит обратить пристальное внимание!