axios é uma biblioteca de solicitação http baseada em Promise, que pode ser usada em navegadores e node.js. Atualmente possui 42 mil estrelas no 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 # 入口文件
Nota: Como os códigos que precisamos examinar são todos arquivos no diretório /lib/
, pesquisaremos em /lib/
todos os caminhos de arquivo a seguir.
interceptoresinterceptores
(Se você estiver familiarizado com middleware, será fácil de entender, porque ele desempenha o papel de middleware baseado em promessas)
Os interceptores são divididos em interceptadores de solicitação e interceptadores de resposta: Os interceptores de solicitação ( interceptors.request
) podem interceptar cada solicitação HTTP especificada e podem modificar o item de configuração ( interceptors.response
) podem ser usados sempre. Após várias solicitações http, intercepte cada solicitação http especificada e os itens de resultado retornados poderão ser modificados.
Aqui está uma breve explicação primeiro e, posteriormente, daremos uma introdução detalhada sobre como interceptar a resposta da solicitação e modificar os parâmetros da solicitação e os dados de resposta.
Conversor de dados (na verdade converte dados, como converter objetos em strings JSON)
Os conversores de dados são divididos em conversores de solicitação e conversores de resposta, como o nome indica: o conversor de solicitação ( transformRequest
) refere-se à conversão de dados antes da solicitação, e o conversor de resposta ( transformResponse
) realiza principalmente a conversão de dados no corpo da resposta após a resposta da solicitação.
adaptador de solicitação http (na verdade, um método)
No projeto axios, o adaptador de solicitação http refere-se principalmente a dois tipos: XHR e http. O núcleo do XHR é o objeto XMLHttpRequest no lado do navegador, e o núcleo do http é o método http[s].request do nó.
É claro que o axios também deixa para o usuário configurar a interface do adaptador por meio da configuração. No entanto, em circunstâncias normais, esses dois adaptadores podem satisfazer a solicitação do navegador para o servidor ou do cliente http do nó para o servidor.
Este compartilhamento se concentra principalmente em XHR.
item de configuração config (na verdade, um objeto)
A configuração da qual estamos falando aqui não é realmente chamada de nome da variável config no projeto. Esse nome é um nome que dei com base no seu propósito de facilitar o entendimento de todos.
No projeto axios, ao definir/ler a configuração, alguns lugares o chamam defaults
( /lib/defaults.js
), aqui está o item de configuração padrão e alguns lugares o chamam de config
, como os parâmetros de Axios.prototype.request
, Outro exemplo são os parâmetros do método adaptador de solicitação http xhrAdapter
.
config é um elo muito importante no projeto axios, e é a principal ponte de “comunicação” entre os usuários e o projeto axios.
(Observação: você pode pular esta seção primeiro e voltar para conferir mais tarde, se precisar)
Existem alguns métodos que são usados em muitos lugares do projeto. Vamos apresentar brevemente esses métodos.
bind ( fn , context ) ;
O efeito de implementação é o mesmo 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 ) ;
O objeto 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 ) ;
O objeto extendObj é:
{
k : 'k2' ,
fn : source . fn . bind ( context ) ,
}
Execute extendObj.fn();
imprima 3
// 首先将axios包引进来
import axios from 'axios'
A primeira maneira de usar: axios(option)
axios ( {
url ,
method ,
headers ,
} )
A segunda maneira de usar: axios(url[, option])
axios ( url , {
method ,
headers ,
} )
O terceiro método de uso (para métodos get、delete
etc.): axios[method](url[, option])
axios . get ( url , {
headers ,
} )
O quarto método de uso (para métodos post、put
etc.): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
A quinta maneira de usar: axios.request(option)
axios . request ( {
url ,
method ,
headers ,
} )
Como arquivo de entrada do projeto axios, vamos primeiro dar uma olhada no código-fonte do axios.js
O núcleo que pode realizar as várias maneiras de usar o 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 ) ;
O código acima Axios.prototype
complicado. Na verdade, createInstance
espera obter uma função This Function aponta para Axios.prototype.request
. apontam para o mesmo objeto.
Então, vamos dar uma olhada no código-fonte do Axios、Axios.prototype.request
?
Axios
é o núcleo do pacote axios. Uma instância Axios
é um aplicativo axios. Outros métodos são extensões do conteúdo Axios
. O método principal do construtor 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
} ) ) ;
} ;
} ) ;
Através do código acima, podemos iniciar solicitações http de várias maneiras: axios()、axios.get()、axios.post()
Em geral, o projeto pode atender à demanda usando a instância de axios exportada padrão. Se a demanda não for atendida, uma nova instância de axios precisa ser criada. O pacote axios também reserva interfaces.
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
Depois de falar sobre por que existem tantas maneiras de usar axios, você pode ter uma pergunta em mente: ao usar axios, não importa o método get
ou o método post
, Axios.prototype.request
é finalmente chamado. este método depende do nosso E quanto à configuração que envia a solicitação?
Antes de começarmos a falar sobre Axios.prototype.request
, vamos primeiro dar uma olhada em como funciona a configuração configurada pelo usuário no projeto axios.
config
mencionada aqui refere-se ao objeto item de configuração em todo o projeto. Através deste objeto, você pode definir:
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
Verifica-se que quase todas as funções dos axios
são configuradas e entregues através deste objeto, que não é apenas a ponte de comunicação dentro do projeto axios
, mas também a ponte de comunicação entre usuários e axios
.
Primeiro, vamos dar uma olhada em como os usuários podem definir itens de configuração:
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 ,
} )
Dê uma olhada na linha de código no método Axios.prototype.request
: ( /lib/core/Axios.js
- linha 35)
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
Pode-se descobrir que os defaults
do objeto de configuração padrão ( /lib/defaults.js
), o atributo da instância Axios this.defaults
e config
do parâmetro request
são mesclados aqui.
A partir disso, a prioridade de múltiplas configurações de baixo para alto é: -> defaults
do objeto de configuração padrão ( /lib/defaults.js
)
—> {método: 'obter' }
-> Propriedade da instância Axios this.defaults
-> config
do parâmetro request
Deixo-vos com uma pergunta: Quando as configurações de defaults
e this.defaults
são iguais e quando são diferentes?
Até agora, obtivemos o objeto config
após merge
vários locais, então como esse objeto é transferido no projeto?
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 ;
} ;
Neste ponto, config
completou sua vida lendária -_-
A próxima seção falará sobre o destaque: Axios.prototype.request
O código aqui é relativamente complicado, e alguns métodos precisam ser rastreados até a fonte para descobrir isso, então você só precisa ter uma compreensão simples da matriz de cadeia. Os interceptores envolvidos e [ dispatchRequest
] serão apresentados em detalhes posteriormente. .
chain
é usado para armazenar o método interceptor e as funções de retorno de chamada Axios.prototype.request
método dispatchRequest
são retiradas do array chain
em sequência por meio de promessas e executadas uma por uma. e A resposta ou erro é enviado. Esta é a missão de Axios.prototype.request
.
Ver código-fonte:
// /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 ;
} ;
Neste ponto, você deve estar cheio de curiosidade sobre o interceptador. O que exatamente é esse interceptador? Vamos descobrir na próxima seção.
// 添加请求拦截器
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'
Em relação aos interceptores, uma breve explicação foi dada na seção Glossário.
Cada instância de axios possui um atributo de instância interceptors
e há dois atributos request
e response
no objeto interceptors
.
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
Ambas as propriedades são uma instância InterceptorManager
e este construtor InterceptorManager
é usado para gerenciar interceptores.
Vamos primeiro dar uma olhada no construtor InterceptorManager
:
O construtor InterceptorManager
é usado para implementar interceptadores. Existem três métodos no protótipo deste construtor: use, eject e forEach. Em relação ao código-fonte, ele é relativamente simples e é usado para operar os atributos de instância dos manipuladores do construtor.
// /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 ) ;
}
} ) ;
} ;
Então, quando adicionamos interceptores por meio de axios.interceptors.request.use
, como o axios permite internamente que esses interceptores obtenham os dados que desejamos antes e depois da solicitação?
Vejamos o código primeiro:
// /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 ;
} ;
Agora você deve ter uma ideia clara do que é um interceptor e como ele funciona no método Axios.prototype.request
. Então, como dispatchRequest
na "posição midstream" envia uma solicitação http?
dispatchRequest faz principalmente três coisas: 1. Obter o objeto de configuração e executar o processamento final na configuração antes de passá-lo para o adaptador de solicitação http. 2. O adaptador de solicitação http inicia uma solicitação com base na configuração de configuração. a solicitação for concluída, se for bem-sucedida. Em seguida, obtenha a resposta após a conversão de dados com base no cabeçalho, dados e config.transformResponse (sobre transformResponse, o conversor de dados abaixo explicará isso) e retorne-a.
// /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 ( /**/ ) ;
} ;
Ok, vendo isso, é hora de resolvermos: como o Axios usa promessas para construir uma ponte assíncrona baseada em XHR?
Como o axios executa o processamento assíncrono por meio do Promise?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
Vamos primeiro fazer um diagrama para entender brevemente o fluxo de sequência para chegar ao usuário após a conclusão da solicitação http no projeto axios:
Pelo motivo pelo qual os axios podem ser usados de várias maneiras, sabemos que não importa como o usuário chame os axios, ele acabará por chamar o método Axios.prototype.request
, que em última análise retorna um objeto 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
chamará dispatchRequest
e dispatchRequest
chamará xhrAdapter
. xhrAdapter
retorna um objeto Promise.
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
Depois que o XHR no xhrAdapter
enviar a solicitação com sucesso, ele executará resolve
do objeto Promise e distribuirá os dados solicitados. Caso contrário, ele executará o método reject
e passará as informações de erro como parâmetro.
// /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 ;
} ;
Verifique se o resultado retornado do servidor passa na verificação:
// /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 ( /**/ ) ;
}
} ;
Retorne ao método dispatchRequest
, primeiro obtenha o objeto Promise retornado pelo método xhrAdapter
e, em seguida response
processe o resultado de sucesso ou falha do objeto Promise retornado por xhrAdapter
por meio do método .then
. falha, então retorna um objeto Promise com status rejected
,
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
Portanto, neste ponto, quando o usuário chama axios()
, ele pode chamar diretamente o .then
ou .catch
do Promise para processamento de negócios.
Olhando para trás, falamos sobre conversão de dados ao introduzir dispatchRequest
e o axios introduziu oficialmente a conversão de dados como um destaque. Então, que efeito a conversão de dados pode ter no uso do 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 ;
}
] ,
} )
Um conversor de solicitação e um conversor de resposta foram customizados no item de configuração defaults
. Dê uma olhada no código-fonte:
// /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 ;
} ] ,
} ;
Então, no projeto axios, onde o conversor é usado?
O conversor de solicitação é usado antes da solicitação http. O conversor de solicitação é usado para processar os dados da solicitação e depois passado para o adaptador de solicitação http.
// /lib/core/dispatchRequest.js
function dispatchRequest ( config ) {
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
return adapter ( config ) . then ( /* ... */ ) ;
} ;
Dê uma olhada no código do método transformData
. Ele percorre principalmente o array do conversor, executa cada conversor separadamente e retorna novos dados de acordo com os parâmetros de dados e cabeçalhos.
// /lib/core/transformData.js
function transformData ( data , headers , fns ) {
utils . forEach ( fns , function transform ( fn ) {
data = fn ( data , headers ) ;
} ) ;
return data ;
} ;
O conversor de resposta é usado para executar o processamento de conversão de dados com base no valor de retorno do adaptador de solicitação http após a conclusão da solicitação 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 ) ;
} ) ;
O interceptador também pode perceber a necessidade de converter dados de solicitação e resposta, mas de acordo com o design do autor e código abrangente, pode-se perceber que durante a solicitação, o interceptador é o principal responsável por modificar os itens de configuração de configuração, e o conversor de dados é principal responsável pela conversão do corpo da solicitação, como a conversão de objetos. Depois de solicitar uma resposta para uma string, o interceptor pode obter response
. O conversor de dados é o principal responsável pelo processamento do corpo da resposta, como a conversão da string em um objeto.
Axios apresenta oficialmente a “conversão automática para dados JSON” como um destaque independente. Então, como o conversor de dados completa essa função? Na verdade é muito simples, vamos dar uma olhada.
Por padrão, os axios serializarão automaticamente o objeto de dados de entrada em uma string JSON e converterão a string JSON nos dados de resposta em um objeto 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 ;
} ]
Neste ponto, o processo de operação do projeto axios foi apresentado. Você já abriu os dois canais de Ren e Du. A seguir, vamos dar uma olhada em quais outras habilidades úteis os axios nos trouxeram.
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 ;
} ) ;
}
O núcleo da função de cancelamento é obter a promise
do atributo da instância por meio de this.promise = new Promise(resolve => resolvePromise = resolve)
/lib/adapters/xhr.js
CancelToken. Neste momento, o status da promise
está pending
. /lib/adapters/xhr.js
Continue a adicionar o método .then
a esta instância promise
no arquivo (linha 159 do arquivo xhr.js
config.cancelToken.promise.then(message => request.abort())
);
Fora CancelToken
, executor
é usado para obter o controle do método cancel
. Desta forma, quando cancel
é executado, o status do atributo promise
da instância pode ser alterado para rejected
, executando assim o request.abort()
método para cancelar a solicitação.
A segunda forma de escrever acima pode ser vista como uma melhoria da primeira forma de escrever, pois muitas vezes nosso método de cancelamento de solicitação é utilizado fora do método de solicitação atual. Por exemplo, se enviarmos duas solicitações A e B, quando a solicitação B. for bem-sucedido, cancele a solicitação 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请求成功了' ) ) ;
Relativamente falando, prefiro a primeira forma de escrever, porque a segunda forma de escrever é muito oculta e não tão intuitiva e fácil de entender como a primeira.
No arquivo /lib/adapters/xhr.js, o parâmetro do método onCanceled não deveria ser chamado de mensagem? Por que é chamado de cancelamento?
No arquivo /lib/adapters/xhr.js, no método onCanceled, as informações de configuração também devem ser transmitidas na rejeição.
import axios from 'axios'
axios . defaults . withCredentials = true ;
Já introduzimos o processo de transferência de configuração no projeto axios na seção sobre como funciona a configuração configurada pelo usuário. A partir disso, podemos concluir que a configuração que fizemos através de axios.defaults.withCredentials = true
está em /lib/adapters/xhr.js
e depois configurado para o item do objeto xhr por meio do código a seguir.
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 ) {
// 超时处理
}
} )
Personalize o intervalo de sucesso e falha do código de status http
import axios from 'axios'
axios . defaults . validateStatus = status => status >= 200 && status < 300 ;
Na configuração padrão, as regras de verificação do código de status HTTP padrão são definidas, portanto, a personalização de validateStatus
é, na verdade, uma substituição do método aqui.
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus : function validateStatus ( status ) {
return status >= 200 && status < 300 ;
} ,
// ...
}
Quando os axios começaram a validar os códigos de status 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
) ) ;
}
} ;
No projeto axios, existem muitas maneiras inteligentes de usar JS, como a operação serial de promessas (é claro, você também pode dizer que isso é baseado nos métodos de processamento de muitos middlewares assíncronos), o que nos permite lidar facilmente com os vários processos antes e depois da solicitação. Controle o fluxo de cada método de processamento; muitas pequenas otimizações práticas, como processamento de dados antes e depois das solicitações, evitando que os programadores escrevam JSON.xxx repetidamente, suportando ambientes de navegador e de nó, para projetos; usando node É sem dúvida excelente.
Resumindo, essa estrela que pode ganhar mais de 42 mil no github (a partir de 27/05/2018) não é de forma alguma uma estimativa de sua força e vale a pena conversar!