axios est une bibliothèque de requêtes http basée sur Promise, qui peut être utilisée dans les navigateurs et node.js. Elle compte actuellement 42 000 étoiles sur 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 # 入口文件
Remarque : étant donné que les codes que nous devons examiner sont tous des fichiers du répertoire /lib/
, nous rechercherons sous /lib/
tous les chemins de fichiers suivants.
intercepteursintercepteurs
(Si vous êtes familier avec le middleware, il sera facile à comprendre, car il joue le rôle de middleware basé sur des promesses)
Les intercepteurs sont divisés en intercepteurs de requêtes et intercepteurs de réponses. Comme leur nom l'indique : les intercepteurs de requêtes ( interceptors.request
) peuvent intercepter chaque requête http spécifiée et peuvent modifier l'élément de configuration. Les intercepteurs de réponse ( interceptors.response
) peuvent être utilisés à chaque fois. Après plusieurs requêtes http, interceptez chaque requête http ou spécifiée, et les éléments de résultat renvoyés peuvent être modifiés.
Voici d'abord une brève explication, puis nous donnerons une introduction détaillée sur la façon d'intercepter la réponse à la demande et de modifier les paramètres de la demande et les données de réponse.
Convertisseur de données (convertit réellement les données, telles que la conversion d'objets en chaînes JSON)
Les convertisseurs de données sont divisés en convertisseurs de requêtes et convertisseurs de réponses. Comme leur nom l'indique : le convertisseur de requêtes ( transformRequest
) fait référence à la conversion des données avant la requête, et le convertisseur de réponse ( transformResponse
) effectue principalement la conversion des données sur le corps de la réponse après la réponse à la requête.
adaptateur de requête http (en fait une méthode)
Dans le projet axios, l'adaptateur de requête http fait principalement référence à deux types : XHR et http. Le cœur de XHR est l'objet XMLHttpRequest côté navigateur, et le cœur de http est la méthode http[s].request du nœud.
Bien entendu, axios laisse également à l'utilisateur le soin de configurer l'interface de l'adaptateur via config. Cependant, dans des circonstances normales, ces deux adaptateurs peuvent satisfaire la demande du navigateur vers le serveur ou du client http du nœud vers le serveur.
Ce partage se concentre principalement sur XHR.
élément de configuration config (en fait un objet)
La configuration dont nous parlons ici ne s'appelle pas vraiment le nom de variable config dans le projet. Ce nom est un nom que j'ai donné en fonction de son objectif pour faciliter la compréhension de chacun.
Dans le projet axios, lors de la configurationlecture de la configuration, certains endroits l'appellent defaults
( /lib/defaults.js
), voici l'élément de configuration par défaut, et certains endroits l'appellent config
, comme les paramètres de Axios.prototype.request
, Un autre exemple concerne les paramètres de xhrAdapter
.
config est un lien très important dans le projet axios, et constitue le principal pont de « communication » entre les utilisateurs et le projet axios.
(Remarque : vous pouvez d'abord ignorer cette section et revenir la consulter plus tard si vous en avez besoin)
Certaines méthodes sont utilisées à de nombreux endroits du projet. Présentons brièvement ces méthodes.
bind ( fn , context ) ;
L'effet d'implémentation est le même que celui 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 ) ;
L'objet mergedObj est :
{
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 ) ;
L'objet extendObj est :
{
k : 'k2' ,
fn : source . fn . bind ( context ) ,
}
Exécutez extendObj.fn();
imprimez 3
// 首先将axios包引进来
import axios from 'axios'
La première façon d'utiliser : axios(option)
axios ( {
url ,
method ,
headers ,
} )
La deuxième façon d'utiliser : axios(url[, option])
axios ( url , {
method ,
headers ,
} )
La troisième méthode d'utilisation (pour les méthodes get、delete
etc.) : axios[method](url[, option])
axios . get ( url , {
headers ,
} )
La quatrième méthode d'utilisation (pour les méthodes post、put
etc.) : axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
La cinquième façon d'utiliser : axios.request(option)
axios . request ( {
url ,
method ,
headers ,
} )
En tant que fichier d'entrée du projet axios, jetons d'abord un coup d'œil au code source d' axios.js
Le noyau qui peut réaliser les différentes manières d'utiliser axios est 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 ) ;
Le code ci-dessus semble alambiqué. En fait, createInstance
espère finalement obtenir une Function. Cette Function pointe vers Axios.prototype.request
. Cette Function aura également chaque méthode sur Axios.prototype
comme méthode statique, et le contexte de ces méthodes est. pointer vers le même objet.
Jetons donc un œil au code source d' Axios、Axios.prototype.request
?
Axios
est le cœur du package axios. Une instance Axios
est une application axios. D'autres méthodes sont des extensions du contenu Axios
. La méthode de base du constructeur Axios
est request
. Diverses méthodes d'appel axios envoient finalement des requêtes via 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
} ) ) ;
} ;
} ) ;
Grâce au code ci-dessus, nous pouvons lancer des requêtes http de différentes manières : axios()、axios.get()、axios.post()
En général, le projet peut répondre à la demande en utilisant l'instance axios exportée par défaut. Si la demande n'est pas satisfaite, une nouvelle instance axios doit être créée. Le package axios réserve également les interfaces.
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
Après avoir expliqué pourquoi il existe tant de façons d'utiliser axios, vous avez peut-être une question en tête : lorsque vous utilisez axios, quelle que soit la méthode get
ou la méthode post
, Axios.prototype.request
est finalement appelée. cette méthode dépend de notre configuration. Qu'en est-il de la configuration qui envoie la demande ?
Avant de commencer à parler d' Axios.prototype.request
, voyons d'abord comment fonctionne la configuration configurée par l'utilisateur dans le projet axios ?
config
mentionnée ici fait référence à l'objet élément de configuration tout au long du projet. Grâce à cet objet, vous pouvez définir :
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
On constate que presque toutes les fonctions d' axios
sont configurées et fournies via cet objet, qui n'est pas seulement le pont de communication au sein du projet axios
, mais aussi le pont de communication entre les utilisateurs et axios
.
Voyons d’abord comment les utilisateurs peuvent définir les éléments de configuration :
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 ,
} )
Jetez un œil à la ligne de code dans Axios.prototype.request
: ( /lib/core/Axios.js
- ligne 35)
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
On peut constater que les defaults
par défaut de l'objet de configuration ( /lib/defaults.js
), l'attribut d'instance Axios this.defaults
et config
du paramètre request
sont fusionnés ici.
À partir de là, la priorité de plusieurs configurations de bas en haut est : —> defaults
de l'objet de configuration par défaut ( /lib/defaults.js
)
—> { méthode : 'obtenir' }
-> Propriété de l'instance Axios this.defaults
-> request
config
des paramètres
Je vous laisse avec une question : quand les configurations de defaults
et this.defaults
sont-elles les mêmes, et quand sont-elles différentes ?
Jusqu'à présent, nous avons obtenu l'objet config
après merge
plusieurs endroits, alors comment cet objet est-il transféré dans le projet ?
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 ;
} ;
À ce stade, config
a terminé sa vie légendaire -_-
La section suivante parlera du point culminant : Axios.prototype.request
Le code ici est relativement compliqué et certaines méthodes doivent être retracées jusqu'à la source pour le comprendre, il vous suffit donc d'avoir une simple compréhension du tableau de chaînes. Les intercepteurs impliqués et [ dispatchRequest
] seront présentés en détail plus tard. .
chain
est utilisé pour contenir la méthode d'interception et la méthode dispatchRequest
. Les fonctions de rappel sont extraites du tableau chain
en séquence via des promesses et exécutées une par une. Enfin, la nouvelle promesse traitée est renvoyée dans Axios.prototype.request
, et La réponse ou l'erreur est envoyée. C'est la mission d' Axios.prototype.request
.
Afficher le code source :
// /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 ;
} ;
À ce stade, vous devez être plein de curiosité à propos de l’intercepteur. Qu’est-ce que cet intercepteur exactement ? Découvrons-le dans la section suivante.
// 添加请求拦截器
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'
Concernant les intercepteurs, une brève explication a été donnée dans la section Glossaire.
Chaque instance axios a un attribut d'instance interceptors
, et il existe deux attributs request
et response
sur l'objet interceptors
.
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
Les deux propriétés sont une instance InterceptorManager
et ce constructeur InterceptorManager
est utilisé pour gérer les intercepteurs.
Jetons d'abord un coup d'œil au constructeur InterceptorManager
:
Le constructeur InterceptorManager
est utilisé pour implémenter les intercepteurs. Il existe trois méthodes sur le prototype de ce constructeur : use, eject et forEach. Concernant le code source, il est en réalité relativement simple et sert à faire fonctionner les attributs d'instance des gestionnaires du constructeur.
// /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 ) ;
}
} ) ;
} ;
Ainsi, lorsque nous ajoutons des intercepteurs via axios.interceptors.request.use
, comment axios permet-il en interne à ces intercepteurs d'obtenir les données souhaitées avant et après la requête ?
Regardons d'abord le code :
// /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 ;
} ;
Maintenant, vous devriez avoir une idée claire de ce qu'est un intercepteur et de son fonctionnement dans la méthode Axios.prototype.request
. Alors, comment dispatchRequest
en « position médiane » envoie-t-il une requête http ?
dispatchRequest fait principalement trois choses : 1. Obtenez l'objet de configuration et effectuez le traitement final sur la configuration avant de le transmettre à l'adaptateur de requête http ; 2. L'adaptateur de requête http initie une requête basée sur la configuration de la configuration. 3. Après l'adaptateur de requête http. la demande est terminée, si elle réussit. Obtenez ensuite la réponse après la conversion des données en fonction de l'en-tête, des données et de config.transformResponse (à propos de transformResponse, le convertisseur de données ci-dessous l'expliquera) et renvoyez-la.
// /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 ( /**/ ) ;
} ;
Bon, voyant cela, il est temps pour nous de faire le tri : comment Axios utilise-t-il les promesses pour construire un pont asynchrone basé sur XHR ?
Comment axios effectue-t-il le traitement asynchrone via Promise ?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
Prenons d'abord un diagramme pour comprendre brièvement le flux de séquence permettant d'atteindre l'utilisateur une fois la requête http terminée dans le projet axios :
Grâce aux raisons pour lesquelles axios peut être utilisé de plusieurs manières, nous savons que quelle que soit la manière dont l'utilisateur appelle axios, il finira par appeler la méthode Axios.prototype.request
, qui renvoie finalement un objet 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
appellera dispatchRequest
et dispatchRequest
appellera xhrAdapter
. xhrAdapter
renvoie un objet Promise.
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
Une fois que le XHR de xhrAdapter
a envoyé avec succès la demande, il exécutera resolve
de l'objet Promise et transmettra les données demandées. Sinon, il exécutera la méthode reject
et transmettra les informations d'erreur en tant que paramètre.
// /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 ;
} ;
Vérifiez si le résultat renvoyé par le serveur réussit la vérification :
// /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 ( /**/ ) ;
}
} ;
Revenez à dispatchRequest
, récupérez d'abord l'objet Promise renvoyé par xhrAdapter
, puis traitez le résultat de réussite ou d'échec de l'objet Promise renvoyé par xhrAdapter
via la méthode .then
. En cas de succès, la response
traitée sera renvoyée. échoue, puis renvoie un objet Promise avec le statut rejected
,
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
Ainsi, à ce stade, lorsque l'utilisateur appelle axios()
, il peut directement appeler .then
ou .catch
de Promise pour le traitement métier.
Avec le recul, nous avons parlé de conversion de données lors de l'introduction dispatchRequest
, et axios a officiellement introduit la conversion de données comme point culminant. Alors, quel effet la conversion de données peut-elle jouer dans l'utilisation d'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 ;
}
] ,
} )
Un convertisseur de requête et un convertisseur de réponse ont été personnalisés dans l'élément de configuration par defaults
. Jetez un œil au code source :
// /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 ;
} ] ,
} ;
Alors dans le projet axios, où est utilisé le convertisseur ?
Le convertisseur de requêtes est utilisé avant la requête http. Le convertisseur de requêtes est utilisé pour traiter les données de la requête, puis transmis à l'adaptateur de requête http pour utilisation.
// /lib/core/dispatchRequest.js
function dispatchRequest ( config ) {
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
return adapter ( config ) . then ( /* ... */ ) ;
} ;
Jetez un œil au code de la méthode transformData
. Elle parcourt principalement le tableau de convertisseurs, exécute chaque convertisseur séparément et renvoie de nouvelles données en fonction des paramètres data et headers.
// /lib/core/transformData.js
function transformData ( data , headers , fns ) {
utils . forEach ( fns , function transform ( fn ) {
data = fn ( data , headers ) ;
} ) ;
return data ;
} ;
Le convertisseur de réponse est utilisé pour effectuer un traitement de conversion de données basé sur la valeur de retour de l'adaptateur de requête http une fois la requête http terminée :
// /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 ) ;
} ) ;
L'intercepteur peut également réaliser la nécessité de convertir les données de requête et de réponse, mais selon la conception et le code complet de l'auteur, on peut voir que lors de la requête, l'intercepteur est principalement responsable de la modification des éléments de configuration de la configuration, et le convertisseur de données est Principalement responsable de la conversion du corps de la demande, comme la conversion d'un objet. Après avoir demandé une réponse pour une chaîne, l'intercepteur peut obtenir response
. Le convertisseur de données est principalement responsable du traitement du corps de la réponse, comme la conversion de la chaîne en objet.
Axios introduit officiellement la « conversion automatique en données JSON » comme point fort indépendant. Alors, comment le convertisseur de données remplit-il cette fonction ? C’est en fait très simple, jetons un coup d’oeil.
Par défaut, axios sérialisera automatiquement l'objet de données entrant en une chaîne JSON et convertira la chaîne JSON dans les données de réponse en un objet 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 ;
} ]
À ce stade, le processus de fonctionnement du projet axios a été présenté. Avez-vous déjà ouvert les deux canaux de Ren et Du. Voyons ensuite quelles autres compétences utiles axios nous a apportées ?
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 ;
} ) ;
}
Le cœur de la fonction d'annulation est d'obtenir la promise
de l'attribut d'instance via this.promise = new Promise(resolve => resolvePromise = resolve)
dans CancelToken À l'heure actuelle, l'état de promise
est pending
via cet attribut, dans /lib/adapters/xhr.js
Continuez à ajouter la méthode .then
à cette instance promise
dans le fichier (ligne 159 du fichier xhr.js
config.cancelToken.promise.then(message => request.abort())
);
En dehors de CancelToken
, executor
est utilisé pour prendre le contrôle de la méthode cancel
de cette manière, lorsque cancel
est exécutée, l'état de l'attribut promise
de l'instance peut être modifié en rejected
, exécutant ainsi la méthode request.abort()
pour annuler la demande.
La deuxième façon d'écrire ci-dessus peut être considérée comme une amélioration de la première façon d'écrire, car notre méthode d'annulation de requête est souvent utilisée en dehors de la méthode de requête actuelle. Par exemple, si nous envoyons deux requêtes A et B, lorsque la requête B. est réussi, , annule la demande 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请求成功了' ) ) ;
Relativement parlant, je préfère la première façon d’écrire, car la deuxième façon d’écrire est trop cachée et pas aussi intuitive et facile à comprendre que la première.
Dans le fichier /lib/adapters/xhr.js, le paramètre de la méthode onCanceled ne doit-il pas être appelé message. Pourquoi s'appelle-t-il Cancel ?
Dans le fichier /lib/adapters/xhr.js, dans la méthode onCanceled, les informations de configuration doivent également être transmises en rejet.
import axios from 'axios'
axios . defaults . withCredentials = true ;
Nous avons introduit le processus de transfert de configuration dans le projet axios dans la section sur le fonctionnement de la configuration configurée par l'utilisateur. De là, nous pouvons conclure que la configuration que nous avons effectuée via axios.defaults.withCredentials = true
se trouve dans /lib/adapters/xhr.js
Il peut être obtenu à partir de /lib/adapters/xhr.js
, puis configuré sur l'élément d'objet xhr via le code suivant.
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 ) {
// 超时处理
}
} )
Personnaliser la plage de réussite et d'échec du code d'état http
import axios from 'axios'
axios . defaults . validateStatus = status => status >= 200 && status < 300 ;
Dans la configuration par défaut, les règles de vérification du code d'état http par défaut sont définies, donc la personnalisation validateStatus
est en fait un remplacement de la méthode ici.
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus : function validateStatus ( status ) {
return status >= 200 && status < 300 ;
} ,
// ...
}
Quand axios a-t-il commencé à valider les codes de statut 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
) ) ;
}
} ;
Dans le projet axios, il existe de nombreuses façons intelligentes d'utiliser JS, comme l'opération en série des promesses (bien sûr, on peut aussi dire que cela est basé sur les méthodes de traitement de nombreux middlewares asynchrones), ce qui nous permet de gérer facilement les divers processus avant et après la demande. Contrôlez le flux de chaque méthode de traitement ; de nombreuses petites optimisations pratiques, telles que le traitement des données avant et après les demandes, évitent aux programmeurs d'écrire JSON.xxx encore et encore, il prend en charge les environnements de navigateur et de nœud, et convient aux projets utilisant node It est sans aucun doute excellent.
Bref, la force de cette star qui peut gagner 42K+ sur github (au 27/05/2018) n'est en aucun cas impossible, et cela vaut la peine d'en parler !