axios — это библиотека HTTP-запросов, основанная на Promise, которую можно использовать в браузерах и node.js. В настоящее время она имеет 42 тысячи звезд на github.
├── /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.
элемент конфигурации конфигурации (фактически объект)
Конфигурация, о которой мы здесь говорим, на самом деле не называется конфигурацией имени переменной в проекте. Это имя я дал, исходя из его цели, чтобы облегчить всем понимание.
В проекте axios при настройкечтении конфигурации в некоторых местах она называется defaults
( /lib/defaults.js
), здесь находится элемент конфигурации по умолчанию, а в некоторых местах она называется config
, например параметры Axios.prototype.request
, Другой пример — параметры метода адаптера http-запроса xhrAdapter
.
config — очень важное звено в проекте axios и основной мост для «коммуникации» между пользователями и проектом axios.
(Примечание: вы можете сначала пропустить этот раздел и вернуться к нему позже, если вам это понадобится)
Есть некоторые методы, которые используются во многих местах проекта. Давайте кратко представим эти методы.
bind ( fn , context ) ;
Эффект реализации такой же, как и у метода Function.prototype.bind
: fn.bind(context)
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 ,
} )
Четвертый метод использования (для методов post、put
и т. д.): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
Пятый способ использования: 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
— это метод request
. Различные методы вызова Axios в конечном итоге отправляют запросы через метод 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. См. следующий код.
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
После разговора о том, почему существует так много способов использования axios, у вас может возникнуть вопрос: при использовании 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
), атрибут экземпляра Axios this.defaults
и config
параметров request
объединены здесь.
Отсюда приоритет нескольких конфигураций от низкого до высокого: -> defaults
по умолчанию для объекта конфигурации ( /lib/defaults.js
).
—> {метод: 'получить' }
—> Свойство экземпляра Axios this.defaults
-> request
config
параметров
Я оставляю вас с вопросом: когда конфигурации defaults
и this.defaults
одинаковы, а когда они различаются?
На данный момент мы получили объект config
после merge
нескольких мест. Как же этот объект переносится в проект?
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
Код здесь относительно сложен, и чтобы разобраться в нем, необходимо отследить некоторые методы до исходного кода, поэтому вам нужно лишь иметь простое представление о цепном массиве. Используемые перехватчики и [ 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. Что касается исходного кода, то он на самом деле относительно прост и используется для управления атрибутами экземпляра обработчиков конструктора.
// /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 в основном выполняет три вещи: 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 выполняет асинхронную обработку через Promise?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
Давайте сначала возьмем диаграмму, чтобы кратко понять последовательность действий пользователя после завершения http-запроса в проекте axios:
Благодаря тому, почему 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 ) {
// ... 省略代码
} ) ;
} ;
После того, как XHR в xhrAdapter
успешно отправит запрос, он выполнит метод resolve
объекта Promise и передаст запрошенные данные. В противном случае он выполнит метод 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
, сначала получите объект Promise, возвращенный методом xhrAdapter
, а затем обработайте результат успеха или неудачи объекта Promise, возвращенный xhrAdapter
, с помощью метода .then
. В случае успеха будет возвращен обработанный response
. терпит неудачу, затем возвращает объект Promise со статусом rejected
,
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
Таким образом, на этом этапе, когда пользователь вызывает метод axios()
, он может напрямую вызвать .then
или .catch
Promise для бизнес-обработки.
Оглядываясь назад, мы говорили о преобразовании данных, когда представляли 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 ;
} ] ;
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» в качестве независимой функции. Так как же конвертер данных выполняет эту функцию? На самом деле все очень просто, давайте посмотрим.
По умолчанию 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 ;
} ) ;
}
Суть функции отмены заключается в получении promise
атрибута экземпляра через this.promise = new Promise(resolve => resolvePromise = resolve)
в CancelToken. В настоящее время статус promise
находится pending
. С помощью этого атрибута в /lib/adapters/xhr.js
Продолжайте добавлять метод .then
к этому экземпляру promise
в файле (строка 159 файла xhr.js
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 информация о конфигурации также должна передаваться при отклонении.
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 ( ) . catch ( error => {
const { message } = error ;
if ( message . indexOf ( 'timeout' ) > - 1 ) {
// 超时处理
}
} )
Настройте диапазон успеха и неудачи кода состояния 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, поддерживающего как браузерную, так и узловую среду для проектов; использование узла. Это, несомненно, отлично.
Короче говоря, эта звезда, которая может набрать 42К+ на github (по состоянию на 27.05.2018), ни в коем случае не является оценкой своих сил и с ней стоит поговорить!