axios es una biblioteca de solicitudes http basada en Promise, que se puede usar en navegadores y node.js. Actualmente tiene 42K estrellas en 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: Debido a que los códigos que necesitamos ver son todos archivos en el directorio /lib/
, buscaremos en /lib/
todas las rutas de archivo siguientes.
interceptoresinterceptores
(Si está familiarizado con el middleware, será fácil de entender porque desempeña el papel de middleware basado en promesas)
Los interceptores se dividen en interceptores de solicitudes e interceptores de respuestas. Como su nombre lo indica: los interceptores de solicitudes ( interceptors.request
) pueden interceptar cada solicitud http o una específica, y pueden modificar el elemento de configuración. Los interceptores de respuesta ( interceptors.response
) se pueden usar cada vez. Después de varias solicitudes http, intercepte cada solicitud http especificada y los elementos de resultados devueltos se podrán modificar.
Aquí hay una breve explicación primero y luego brindaremos una introducción detallada sobre cómo interceptar la respuesta de la solicitud y modificar los parámetros de la solicitud y los datos de la respuesta.
Convertidor de datos (en realidad convierte datos, como convertir objetos en cadenas JSON)
Los convertidores de datos se dividen en convertidores de solicitudes y convertidores de respuestas. Como su nombre lo indica: el convertidor de solicitudes ( transformRequest
) se refiere a la conversión de datos antes de la solicitud, y el convertidor de respuestas ( transformResponse
) realiza principalmente la conversión de datos en el cuerpo de la respuesta después de la respuesta de la solicitud.
Adaptador de solicitud http (en realidad, un método)
En el proyecto axios, el adaptador de solicitud http se refiere principalmente a dos tipos: XHR y http. El núcleo de XHR es el objeto XMLHttpRequest en el lado del navegador, y el núcleo de http es el método http[s].request del nodo.
Por supuesto, axios también deja que el usuario configure la interfaz del adaptador a través de la configuración. Sin embargo, en circunstancias normales, estos dos adaptadores pueden satisfacer la solicitud del navegador al servidor o del cliente http del nodo al servidor.
Este intercambio se centra principalmente en XHR.
elemento de configuración config (en realidad un objeto)
La configuración de la que estamos hablando aquí en realidad no se llama configuración de nombre de variable en el proyecto. Este nombre es un nombre que le di según su propósito para facilitar la comprensión de todos.
En el proyecto axios, al configurarleer la configuración, algunos lugares lo llaman defaults
( /lib/defaults.js
), aquí está el elemento de configuración predeterminado, y algunos lugares lo llaman config
, como los parámetros de Axios.prototype.request
, Otro ejemplo son los parámetros del método del adaptador de solicitud http xhrAdapter
.
config es un vínculo muy importante en el proyecto axios y es el puente principal para la "comunicación" entre los usuarios y el proyecto axios.
(Nota: puedes saltarte esta sección primero y volver para revisarla más tarde si la necesitas)
Hay algunos métodos que se utilizan en muchos lugares del proyecto. Presentemos brevemente estos métodos.
bind ( fn , context ) ;
El efecto de implementación es el mismo que 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 ) ;
El objeto fusionadoObj es:
{
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 ) ;
El objeto extendObj es:
{
k : 'k2' ,
fn : source . fn . bind ( context ) ,
}
Ejecute extendObj.fn();
imprima 3
// 首先将axios包引进来
import axios from 'axios'
La primera forma de uso: axios(option)
axios ( {
url ,
method ,
headers ,
} )
La segunda forma de uso: axios(url[, option])
axios ( url , {
method ,
headers ,
} )
El tercer método de uso (para métodos de get、delete
etc.): axios[method](url[, option])
axios . get ( url , {
headers ,
} )
El cuarto método de uso (para métodos post、put
etc.): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
La quinta forma de uso: axios.request(option)
axios . request ( {
url ,
method ,
headers ,
} )
Como archivo de entrada del proyecto axios, primero echemos un vistazo al código fuente de axios.js
El núcleo que puede implementar varias formas de usar axios es 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 ) ;
El código anterior parece complicado. De hecho, createInstance
finalmente espera obtener una función. Esta función apunta a Axios.prototype.request
. Esta función también tendrá todos los métodos en Axios.prototype
como métodos estáticos, y el contexto de estos métodos es. apuntar al mismo objeto.
Entonces, echemos un vistazo al código fuente de Axios、Axios.prototype.request
.
Axios
es el núcleo del paquete axios. Una instancia Axios
es una aplicación de axios. Otros métodos son extensiones del contenido Axios
. El método principal del constructor Axios
es 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
} ) ) ;
} ;
} ) ;
A través del código anterior, podemos iniciar solicitudes http de varias maneras: axios()、axios.get()、axios.post()
En general, el proyecto puede satisfacer la demanda utilizando la instancia de axios exportada predeterminada. Si no se cumple la demanda, es necesario crear una nueva instancia de axios. El paquete axios también reserva interfaces.
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
Después de hablar sobre por qué hay tantas formas de usar axios, es posible que tenga una pregunta en mente: cuando se usa axios, no importa el método get
o el método post
, finalmente se llama Axios.prototype.request
. Este método depende de nuestra ¿Qué pasa con la configuración que envía la solicitud?
Antes de comenzar a hablar sobre Axios.prototype.request
, primero echemos un vistazo a cómo funciona la configuración configurada por el usuario en el proyecto axios.
config
mencionada aquí se refiere al objeto del elemento de configuración durante todo el proyecto. A través de este objeto, puede configurar:
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
Se puede encontrar que casi todas las funciones de axios
se configuran y entregan a través de este objeto, que no solo es el puente de comunicación dentro del proyecto axios
, sino también el puente de comunicación entre usuarios y axios
.
Primero, echemos un vistazo a cómo los usuarios pueden definir elementos de configuración:
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 ,
} )
Eche un vistazo a la línea de código en Axios.prototype.request
: ( /lib/core/Axios.js
- línea 35)
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
Se puede encontrar que el objeto de configuración predeterminado defaults
( /lib/defaults.js
), el atributo de instancia de Axios this.defaults
y config
del parámetro request
se combinan aquí.
A partir de esto, la prioridad de múltiples configuraciones de menor a mayor es: -> defaults
del objeto de configuración predeterminado ( /lib/defaults.js
)
—> {método: 'obtener'}
-> Propiedad de instancia de Axios this.defaults
-> config
del parámetro request
Les dejo una pregunta: ¿Cuándo son iguales las configuraciones de defaults
y this.defaults
, y cuándo son diferentes?
Hasta ahora, hemos obtenido el objeto config
después de merge
varios lugares, entonces, ¿cómo se transfiere este objeto en el proyecto?
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 ;
} ;
En este punto, config
ha completado su vida legendaria -_-
La siguiente sección hablará sobre lo más destacado: Axios.prototype.request
El código aquí es relativamente complicado y algunos métodos deben rastrearse hasta la fuente para comprenderlo, por lo que solo necesita tener una comprensión simple de la matriz de cadenas. Los interceptores involucrados y [ dispatchRequest
] se presentarán en detalle más adelante. .
chain
se utiliza para contener el método interceptor y el método dispatchRequest
. Las funciones de devolución de llamada se extraen de chain
en secuencia a través de promesas y se ejecutan una por una. Finalmente, la nueva promesa procesada se devuelve en Axios.prototype.request
. y Se envía la respuesta o el error. Esta es la misión de Axios.prototype.request
.
Ver código fuente:
// /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 ;
} ;
En este punto, debe estar lleno de curiosidad sobre el interceptor. ¿Qué es exactamente este interceptor? Lo descubriremos en la siguiente sección.
// 添加请求拦截器
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'
Respecto a los interceptores, se ha dado una breve explicación en la sección del Glosario.
Cada instancia de axios tiene un atributo de instancia interceptors
y hay dos atributos request
y response
en el objeto interceptors
.
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
Ambas propiedades son una instancia InterceptorManager
y este constructor InterceptorManager
se utiliza para administrar interceptores.
Primero echemos un vistazo al constructor InterceptorManager
:
El constructor InterceptorManager
se utiliza para implementar interceptores. Hay tres métodos en el prototipo de este constructor: usar, expulsar y forEach. En cuanto al código fuente, en realidad es relativamente simple y se utiliza para operar los atributos de instancia del controlador del constructor.
// /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 ) ;
}
} ) ;
} ;
Entonces, cuando agregamos interceptores a través de axios.interceptors.request.use
, ¿cómo permite axios internamente que estos interceptores obtengan los datos que queremos antes y después de la solicitud?
Veamos primero el código:
// /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 ;
} ;
Ahora, debería tener una idea clara de qué es un interceptor y cómo funciona en el método Axios.prototype.request
. Entonces, ¿cómo envía dispatchRequest
en la "posición intermedia" una solicitud http?
DispatchRequest hace principalmente tres cosas: 1. Obtener el objeto de configuración y realizar el procesamiento final en la configuración antes de pasarlo al adaptador de solicitud http. 2. El adaptador de solicitud http inicia una solicitud basada en la configuración de configuración. 3. Después del adaptador de solicitud http. la solicitud se completa, si tiene éxito Luego obtenga la respuesta después de la conversión de datos según el encabezado, los datos y config.transformResponse (sobre transformResponse, el convertidor de datos a continuación lo explicará) y devuélvalo.
// /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 ( /**/ ) ;
} ;
Bien, viendo esto, es hora de que averigüemos: ¿Cómo utiliza Axios las promesas para construir un puente asincrónico basado en XHR?
¿Cómo realiza axios el procesamiento asincrónico a través de Promise?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
Primero tomemos un diagrama para comprender brevemente el flujo de secuencia para llegar al usuario después de que se completa la solicitud http en el proyecto axios:
Al comprender por qué axios se puede usar de muchas maneras, sabemos que no importa cómo el usuario llame a axios, finalmente llamará al método Axios.prototype.request
, que finalmente devuelve un 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
llamará dispatchRequest
y dispatchRequest
llamará xhrAdapter
. xhrAdapter
devuelve un objeto Promise.
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
Después de que XHR en xhrAdapter
envíe con éxito la solicitud, ejecutará resolve
del objeto Promise y pasará los datos solicitados. De lo contrario, ejecutará el método reject
y pasará la información de error 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 si el resultado devuelto por el servidor pasa la verificación:
// /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 ( /**/ ) ;
}
} ;
Regrese al método dispatchRequest
, primero obtenga el objeto Promise devuelto por xhrAdapter
y luego procese el resultado de éxito o falla del objeto Promise devuelto por xhrAdapter
a través del método .then
. Si tiene éxito, se devolverá la response
procesada. falla, luego devuelve un objeto de promesa con estado rejected
,
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
Entonces, en este punto, cuando el usuario llama axios()
, puede llamar directamente a .then
o .catch
de Promise para el procesamiento comercial.
Mirando hacia atrás, hablamos sobre la conversión de datos cuando presentamos dispatchRequest
, y Axios presentó oficialmente la conversión de datos como un punto destacado. Entonces, ¿qué efecto puede tener la conversión de datos al usar 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 ;
}
] ,
} )
Se han personalizado un convertidor de solicitudes y un convertidor de respuestas en el elemento de configuración defaults
. Eche un vistazo al código fuente:
// /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 ;
} ] ,
} ;
Entonces, en el proyecto axios, ¿dónde se utiliza el convertidor?
El convertidor de solicitudes se utiliza antes de la solicitud http. El convertidor de solicitudes se utiliza para procesar los datos de la solicitud y luego se pasa al adaptador de solicitud http para su uso.
// /lib/core/dispatchRequest.js
function dispatchRequest ( config ) {
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
return adapter ( config ) . then ( /* ... */ ) ;
} ;
Eche un vistazo al código del método transformData
. Principalmente atraviesa la matriz del convertidor, ejecuta cada convertidor por separado y devuelve nuevos datos de acuerdo con los parámetros de datos y encabezados.
// /lib/core/transformData.js
function transformData ( data , headers , fns ) {
utils . forEach ( fns , function transform ( fn ) {
data = fn ( data , headers ) ;
} ) ;
return data ;
} ;
El convertidor de respuesta se utiliza para realizar el procesamiento de conversión de datos en función del valor de retorno del adaptador de solicitud http después de que se completa la solicitud 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 ) ;
} ) ;
El interceptor también puede darse cuenta de la necesidad de convertir datos de solicitud y respuesta, pero de acuerdo con el diseño del autor y el código completo, se puede ver que durante la solicitud, el interceptor es el principal responsable de modificar los elementos de configuración, y el convertidor de datos es Principalmente responsable de convertir el cuerpo de la solicitud, como convertir objetos. Después de solicitar una respuesta para una cadena, el interceptor puede obtener response
. El convertidor de datos es el principal responsable de procesar el cuerpo de la respuesta, como convertir la cadena en un objeto.
Axios presenta oficialmente la "conversión automática a datos JSON" como un punto destacado independiente. Entonces, ¿cómo completa esta función el convertidor de datos? En realidad es muy simple, echemos un vistazo.
De forma predeterminada, axios serializará automáticamente el objeto de datos entrante en una cadena JSON y convertirá la cadena JSON en los datos de respuesta en un 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 ;
} ]
Hasta ahora, se ha introducido el proceso operativo del proyecto axios. ¿Ya ha abierto los dos canales de Ren y Du? A continuación, echemos un vistazo a otras habilidades útiles que nos ha aportado 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 ;
} ) ;
}
El núcleo de la función de cancelación es obtener la promise
del atributo de instancia a través de this.promise = new Promise(resolve => resolvePromise = resolve)
en CancelToken. En este momento, el estado de promise
está pending
a través de este atributo, en /lib/adapters/xhr.js
Continúe agregando el método .then
a esta instancia promise
en el archivo (línea 159 del archivo xhr.js
config.cancelToken.promise.then(message => request.abort())
);
Fuera de CancelToken
, executor
se usa para obtener control del método cancel
. De esta manera, cuando se ejecuta cancel
, el estado del atributo promise
de la instancia se puede cambiar a rejected
, ejecutando así request.abort()
método para cancelar la solicitud.
La segunda forma de escritura anterior puede verse como una mejora de la primera forma de escritura, porque muchas veces nuestro método de cancelación de solicitud se usa fuera del método de solicitud actual. Por ejemplo, si enviamos dos solicitudes A y B, cuando se envía la solicitud B. es exitoso, cancele la solicitud 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 hablando, prefiero la primera forma de escribir, porque la segunda forma de escribir está demasiado oculta y no es tan intuitiva y fácil de entender como la primera.
En el archivo /lib/adapters/xhr.js, ¿no debería llamarse mensaje al parámetro del método onCanceled? ¿Por qué se llama cancelar?
En el archivo /lib/adapters/xhr.js, en el método onCanceled, la información de configuración también debe enviarse como rechazo.
import axios from 'axios'
axios . defaults . withCredentials = true ;
Ya hemos introducido el proceso de transferencia de configuración en el proyecto axios en la sección sobre cómo funciona la configuración configurada por el usuario. De esto, podemos concluir que la configuración que realizamos a través de axios.defaults.withCredentials = true
está en /lib/adapters/xhr.js
y luego configurarlo en el elemento del objeto xhr mediante el siguiente código.
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 ) {
// 超时处理
}
} )
Personalice el rango de éxito y fracaso del código de estado http
import axios from 'axios'
axios . defaults . validateStatus = status => status >= 200 && status < 300 ;
En la configuración predeterminada, se definen las reglas de verificación del código de estado http predeterminadas, por lo que personalizar validateStatus
es en realidad una anulación del método aquí.
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus : function validateStatus ( status ) {
return status >= 200 && status < 300 ;
} ,
// ...
}
¿Cuándo comenzó axios a validar códigos de estado 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
) ) ;
}
} ;
En el proyecto axios, hay muchas formas inteligentes de usar JS, como la operación en serie de promesas (por supuesto, también se puede decir que esto se basa en los métodos de procesamiento de muchos middleware asincrónicos), lo que nos permite manejar fácilmente varios procesos antes y después de la solicitud Controlar el flujo de cada método de procesamiento; muchas optimizaciones pequeñas prácticas, como el procesamiento de datos antes y después de las solicitudes, lo que evita que los programadores escriban JSON.xxx una y otra vez para soportar entornos de navegador y de nodo; usando node Sin duda es excelente.
En resumen, esta estrella que puede ganar más de 42K en github (a partir del 27.05.2018) no es de ninguna manera una estimación de su fuerza y ¡vale la pena hablar con ella!