axios ist eine auf Promise basierende HTTP-Anfragebibliothek, die in Browsern und node.js verwendet werden kann. Sie hat derzeit 42.000 Sterne auf 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 # 入口文件
Hinweis: Da es sich bei den Codes, die wir betrachten müssen, um alle Dateien im Verzeichnis /lib/
handelt, suchen wir unter /lib/
nach allen folgenden Dateipfaden.
AbfangjägerAbfangjäger
(Wenn Sie mit Middleware vertraut sind, ist es leicht zu verstehen, da sie die Rolle einer versprechensbasierten Middleware spielt.)
Abfangjäger werden in Anforderungs-Abfangjäger und Antwort-Abfangjäger unterteilt: Anforderungs-Abfangjäger ( interceptors.request
) können jede einzelne HTTP-Anfrage abfangen und das Konfigurationselement ( interceptors.response
) jederzeit ändern. Nach mehreren http-Anfragen wird jede einzelne oder angegebene http-Anfrage abgefangen, und die zurückgegebenen Ergebniselemente können geändert werden.
Hier ist zunächst eine kurze Erklärung. Später geben wir eine detaillierte Einführung in das Abfangen der Anforderungsantwort und das Ändern der Anforderungsparameter und Antwortdaten.
Datenkonverter (konvertiert tatsächlich Daten, z. B. Konvertieren von Objekten in JSON-Strings)
Datenkonverter werden in Anforderungskonverter und Antwortkonverter unterteilt: Der Anforderungskonverter ( transformRequest
) bezieht sich auf die Konvertierung von Daten vor der Anforderung, und der Antwortkonverter ( transformResponse
) führt hauptsächlich die Datenkonvertierung für den Antworttext nach der Anforderungsantwort durch.
http-Anforderungsadapter (eigentlich eine Methode)
Im Axios-Projekt bezieht sich der HTTP-Anforderungsadapter hauptsächlich auf zwei Typen: XHR und http. Der Kern von XHR ist das XMLHttpRequest-Objekt auf der Browserseite, und der Kern von http ist die http[s].request-Methode des Knotens.
Natürlich überlässt axios es dem Benutzer, die Adapterschnittstelle über config zu konfigurieren. Unter normalen Umständen können diese beiden Adapter jedoch die Anforderung vom Browser an den Server oder vom HTTP-Client des Knotens an den Server erfüllen.
Dieser Austausch konzentriert sich hauptsächlich auf XHR.
config-Konfigurationselement (eigentlich ein Objekt)
Die Konfiguration, über die wir hier sprechen, wird im Projekt nicht wirklich als Variablenname config bezeichnet. Dieser Name ist ein Name, den ich aufgrund seines Zwecks vergeben habe, um das Verständnis für alle zu erleichtern.
Im Axios-Projekt wird es beim Festlegen/Lesen der Konfiguration an einigen Stellen defaults
( /lib/defaults.js
) bezeichnet. Hier ist das Standardkonfigurationselement und an einigen Stellen wird es als config
bezeichnet, z. B. die Parameter von Axios.prototype.request
Ein weiteres Beispiel sind die Parameter xhrAdapter
.
config ist ein sehr wichtiger Link im Axios-Projekt und die Hauptbrücke für die „Kommunikation“ zwischen Benutzern und dem Axios-Projekt.
(Hinweis: Sie können diesen Abschnitt zunächst überspringen und bei Bedarf später noch einmal durchsehen.)
Es gibt einige Methoden, die an vielen Stellen im Projekt verwendet werden. Lassen Sie uns diese Methoden kurz vorstellen.
bind ( fn , context ) ;
Der Implementierungseffekt ist der gleiche wie bei Function.prototype.bind
-Methode: 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 ) ;
Das mergedObj-Objekt ist:
{
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 ) ;
Das extendObj-Objekt ist:
{
k : 'k2' ,
fn : source . fn . bind ( context ) ,
}
extendObj.fn();
ausführen, 3
drucken
// 首先将axios包引进来
import axios from 'axios'
Die erste Möglichkeit zur Verwendung: axios(option)
axios ( {
url ,
method ,
headers ,
} )
Die zweite Möglichkeit: axios(url[, option])
axios ( url , {
method ,
headers ,
} )
Die dritte Verwendungsmethode (für Methoden get、delete
usw.): axios[method](url[, option])
axios . get ( url , {
headers ,
} )
Die vierte Verwendungsmethode (für post、put
usw. Methoden): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
Die fünfte Verwendungsmöglichkeit: axios.request(option)
axios . request ( {
url ,
method ,
headers ,
} )
Als Einstiegsdatei des Axios-Projekts werfen wir zunächst einen Blick auf den Quellcode von axios.js
Der Kern, der die verschiedenen Verwendungsmöglichkeiten von axios realisieren kann, ist 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 ) ;
Der obige Code scheint kompliziert zu sein. Tatsächlich hofft createInstance
, eine Funktion zu erhalten, Axios.prototype
auf Axios.prototype.request
verweist, und der Kontext dieser Methoden ist zeigen auf dasselbe Objekt.
Werfen wir also einen Blick auf den Quellcode von Axios、Axios.prototype.request
?
Axios
ist der Kern des Axios-Pakets. Andere Methoden sind Erweiterungen Axios
Axios
. Verschiedene Axios
-Aufrufmethoden senden Anforderungen über request
request
Methode.
// /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
} ) ) ;
} ;
} ) ;
Mit dem obigen Code können wir HTTP-Anfragen auf verschiedene Arten initiieren: axios()、axios.get()、axios.post()
Im Allgemeinen kann das Projekt die Anforderung erfüllen, indem die standardmäßig exportierte Axios-Instanz verwendet wird. Wenn die Anforderung nicht erfüllt wird, muss eine neue Axios-Instanz erstellt werden. Siehe den folgenden Code.
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
Nachdem Sie darüber gesprochen haben, warum es so viele Möglichkeiten gibt, Axios zu verwenden, haben Sie möglicherweise eine Frage: Wenn Sie Axios verwenden, wird Axios.prototype.request
letztendlich unabhängig von der get
-Methode oder der post
-Methode aufgerufen Diese Methode hängt von unserem ab. Was ist mit der Konfigurationskonfiguration, die die Anfrage sendet?
Bevor wir über Axios.prototype.request
sprechen, werfen wir zunächst einen Blick darauf, wie die vom Benutzer konfigurierte Konfiguration im Axios-Projekt funktioniert.
config
bezieht sich auf das Konfigurationselementobjekt im gesamten Projekt. Über dieses Objekt können Sie Folgendes festlegen:
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
Es kann festgestellt werden, dass fast alle Funktionen von axios
über dieses Objekt konfiguriert und bereitgestellt werden, das nicht nur die Kommunikationsbrücke innerhalb des axios
Projekts, sondern auch die Kommunikationsbrücke zwischen Benutzern und axios
darstellt.
Schauen wir uns zunächst an, wie Benutzer Konfigurationselemente definieren können:
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 ,
} )
Schauen Sie sich die Codezeile in Axios.prototype.request
-Methode an: ( /lib/core/Axios.js
- Zeile 35)
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
Es kann festgestellt werden, dass die Standardkonfigurationsobjekte defaults
( /lib/defaults.js
), Axios-Instanzattribut this.defaults
und request
config
hier zusammengeführt werden.
Daraus ergibt sich die Priorität mehrerer Konfigurationen von niedrig nach hoch: —> defaults
( /lib/defaults.js
)
—> { Methode: 'get' }
—> Axios-Instanzeigenschaft this.defaults
—> config
request
Ich lasse Sie mit einer Frage zurück: Wann sind die Konfigurationen von defaults
und this.defaults
gleich und wann unterscheiden sie sich?
Bisher haben wir das config
nach merge
mehrerer Orte erhalten. Wie wird dieses Objekt also im Projekt übertragen?
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 ;
} ;
Zu diesem Zeitpunkt hat config
sein legendäres Leben beendet -_-
Im nächsten Abschnitt geht es um das Highlight: Axios.prototype.request
Der Code hier ist relativ kompliziert und einige Methoden müssen bis zur Quelle zurückverfolgt werden, um ihn herauszufinden. Sie müssen also nur ein einfaches Verständnis des Kettenarrays haben. Die beteiligten Interceptoren und [ dispatchRequest
] werden später im Detail vorgestellt .
chain
-Array wird zum Speichern der Interceptor-Methode und dispatchRequest
Methode verwendet. Die Callback-Funktionen werden nacheinander aus chain
Array entnommen und einzeln ausgeführt. Schließlich wird das verarbeitete neue Promise in Axios.prototype.request
-Methode zurückgegeben. und Die Antwort oder der Fehler wird gesendet. Dies ist die Aufgabe von Axios.prototype.request
.
Quellcode ansehen:
// /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 ;
} ;
An diesem Punkt müssen Sie voller Neugier auf den Abfangjäger sein. Was genau ist dieser Abfangjäger?
// 添加请求拦截器
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'
In Bezug auf Abfangjäger wurde im Abschnitt „Glossar“ eine kurze Erläuterung gegeben.
Jede Axios-Instanz verfügt über ein interceptors
-Instanzattribut, und es gibt zwei Attribute request
und response
für das interceptors
-Objekt.
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
Beide Eigenschaften sind eine InterceptorManager
Instanz, und dieser InterceptorManager
Konstruktor wird zum Verwalten von Interceptoren verwendet.
Werfen wir zunächst einen Blick auf den InterceptorManager
-Konstruktor:
Der InterceptorManager
-Konstruktor wird zum Implementieren von Interceptoren verwendet. Der Prototyp dieses Konstruktors verfügt über drei Methoden: use, eject und forEach. Der Quellcode ist eigentlich relativ einfach und wird zum Betrieb der Handler-Instanzattribute des Konstruktors verwendet.
// /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 ) ;
}
} ) ;
} ;
Wenn wir also Interceptoren über axios.interceptors.request.use
hinzufügen, wie ermöglicht Axios diesen Interceptoren intern, die gewünschten Daten vor und nach der Anfrage abzurufen?
Schauen wir uns zunächst den Code an:
// /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 ;
} ;
Jetzt sollten Sie eine klare Vorstellung davon haben, was ein Interceptor ist und wie er in der Axios.prototype.request
-Methode funktioniert. Wie sendet dispatchRequest
in der „Midstream-Position“ eine http-Anfrage?
„dispatchRequest“ führt hauptsächlich drei Dinge aus: 1. Rufen Sie das Konfigurationsobjekt ab und führen Sie die endgültige Verarbeitung der Konfiguration durch, bevor Sie es an den HTTP-Anforderungsadapter übergeben. 2. Der HTTP-Anforderungsadapter initiiert eine Anforderung basierend auf der Konfigurationskonfiguration Die Anfrage ist abgeschlossen, wenn sie erfolgreich ist. Rufen Sie dann die Antwort nach der Datenkonvertierung basierend auf Header, Daten und config.transformResponse ab (zu transformResponse erklärt der Datenkonverter unten dies) und geben Sie sie zurück.
// /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 ( /**/ ) ;
} ;
Okay, angesichts dessen ist es an der Zeit, dass wir uns mit der Frage befassen: Wie nutzt Axios Versprechen, um eine asynchrone Brücke auf Basis von XHR zu bauen?
Wie führt Axios eine asynchrone Verarbeitung durch Promise durch?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
Lassen Sie uns zunächst ein Diagramm erstellen, um den Ablauf des Erreichens des Benutzers nach Abschluss der http-Anfrage im Axios-Projekt kurz zu verstehen:
Durch die vielfältigen Einsatzmöglichkeiten von Axios wissen wir, dass der Benutzer unabhängig davon, wie er Axios aufruft, letztendlich die Methode Axios.prototype.request
aufruft, die letztendlich ein Promise-Objekt zurückgibt.
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
ruft dispatchRequest
auf, und dispatchRequest
ruft xhrAdapter
auf. xhrAdapter
gibt ein Promise-Objekt zurück.
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
Nachdem der XHR in xhrAdapter
die Anfrage erfolgreich gesendet hat, führt er resolve
Auflösungsmethode des Promise-Objekts aus und übergibt die angeforderten Daten. Andernfalls führt er die reject
aus und übergibt die Fehlerinformationen als Parameter.
// /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 ;
} ;
Überprüfen Sie, ob das Rückgabeergebnis vom Server die Überprüfung besteht:
// /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 ( /**/ ) ;
}
} ;
Kehren Sie zur dispatchRequest
Methode zurück, rufen Sie zuerst das von xhrAdapter
-Methode zurückgegebene Promise-Objekt ab und verarbeiten Sie dann das Erfolgs- oder Fehlerergebnis des von xhrAdapter
zurückgegebenen Promise-Objekts. Bei Erfolg wird .then
verarbeitete response
zurückgegeben schlägt fehl und gibt dann ein Promise-Objekt mit dem Status rejected
zurück.
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
Wenn der Benutzer zu diesem Zeitpunkt axios()
Methode aufruft, kann er für die Geschäftsverarbeitung direkt Promises .then
oder .catch
aufrufen.
Rückblickend haben wir bei der Einführung dispatchRequest
über die Datenkonvertierung gesprochen und Axios hat die Datenkonvertierung offiziell als Highlight eingeführt. Welchen Effekt kann die Datenkonvertierung also bei der Verwendung von Axios haben?
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 ;
}
] ,
} )
Im defaults
wurden ein Anforderungskonverter und ein Antwortkonverter angepasst. Schauen Sie sich den Quellcode an:
// /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 ;
} ] ,
} ;
Wo wird im Axios-Projekt der Konverter verwendet?
Der Anforderungskonverter wird vor der HTTP-Anforderung verwendet. Der Anforderungskonverter wird zur Verarbeitung der Anforderungsdaten verwendet und dann zur Verwendung an den HTTP-Anforderungsadapter übergeben.
// /lib/core/dispatchRequest.js
function dispatchRequest ( config ) {
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
return adapter ( config ) . then ( /* ... */ ) ;
} ;
Schauen Sie sich den Code der transformData
-Methode an. Sie durchläuft hauptsächlich das Konverter-Array, führt jeden Konverter separat aus und gibt neue Daten gemäß den Daten- und Header-Parametern zurück.
// /lib/core/transformData.js
function transformData ( data , headers , fns ) {
utils . forEach ( fns , function transform ( fn ) {
data = fn ( data , headers ) ;
} ) ;
return data ;
} ;
Der Antwortkonverter wird verwendet, um eine Datenkonvertierungsverarbeitung basierend auf dem Rückgabewert des HTTP-Anforderungsadapters durchzuführen, nachdem die HTTP-Anforderung abgeschlossen ist:
// /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 ) ;
} ) ;
Der Interceptor kann auch die Notwendigkeit erkennen, Anforderungs- und Antwortdaten zu konvertieren. Gemäß dem Entwurf und dem umfassenden Code des Autors ist jedoch ersichtlich, dass der Interceptor während der Anforderung hauptsächlich für die Änderung der Konfigurationskonfigurationselemente verantwortlich ist und der Datenkonverter dies tut Der Interceptor ist hauptsächlich für die Konvertierung des Anforderungshauptteils verantwortlich, z. B. für die Konvertierung von Objekten, nachdem er eine response
für eine Zeichenfolge angefordert hat.
Als eigenständiges Highlight führt Axios offiziell die „automatische Konvertierung in JSON-Daten“ ein. Wie vervollständigt der Datenkonverter diese Funktion? Es ist eigentlich ganz einfach, werfen wir einen Blick darauf.
Standardmäßig serialisiert Axios das eingehende Datenobjekt automatisch in einen JSON-String und konvertiert den JSON-String in den Antwortdaten in ein JavaScript-Objekt.
// 请求时,将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 ;
} ]
An dieser Stelle wurde der Betriebsprozess des Axios-Projekts vorgestellt. Haben Sie bereits die beiden Kanäle Ren und Du geöffnet? Schauen wir uns als Nächstes an, welche anderen nützlichen Fähigkeiten Axios uns gebracht hat.
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 ;
} ) ;
}
Der Kern der Abbruchfunktion besteht darin /lib/adapters/xhr.js
das Instanzattribut- promise
über this.promise = new Promise(resolve => resolvePromise = resolve)
in CancelToken abzurufen. Zu diesem Zeitpunkt ist der Status promise
pending
/lib/adapters/xhr.js
Fügen Sie dieser promise
Instanz in der Datei weiterhin die .then
-Methode hinzu (Zeile 159 der xhr.js
-Datei config.cancelToken.promise.then(message => request.abort())
);
Außerhalb CancelToken
wird executor
Parameter verwendet, um die Kontrolle über die cancel
-Methode zu erlangen. Auf diese Weise kann der Status cancel
promise
Attributs der Instanz in rejected
geändert werden, wodurch request.abort()
ausgeführt wird. request.abort()
-Methode zum Abbrechen der Anfrage.
Die obige zweite Schreibweise kann als Verbesserung der ersten Schreibweise angesehen werden, da unsere Anforderungsstornierungsmethode häufig außerhalb der aktuellen Anforderungsmethode verwendet wird. Wenn wir beispielsweise zwei Anforderungen A und B senden, wenn die Anforderung B ist erfolgreich, Anfrage A abbrechen.
// 第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请求成功了' ) ) ;
Relativ gesehen bevorzuge ich die erste Schreibweise, da die zweite Schreibweise zu versteckt und nicht so intuitiv und leicht verständlich ist wie die erste.
Sollte in der Datei /lib/adapters/xhr.js der Parameter der onCanceled-Methode nicht message heißen? Warum heißt er cancel?
In der Datei /lib/adapters/xhr.js sollten in der onCanceled-Methode die Konfigurationsinformationen auch in Reject ausgegeben werden.
import axios from 'axios'
axios . defaults . withCredentials = true ;
Wir haben den Übertragungsprozess der Konfiguration im Axios-Projekt bereits im Abschnitt zur Funktionsweise der vom Benutzer konfigurierten Konfiguration vorgestellt. Daraus können wir schließen, dass die Konfiguration, die wir über axios.defaults.withCredentials = true
vorgenommen haben, in /lib/adapters/xhr.js
liegt. /lib/adapters/xhr.js
und dann über den folgenden Code für das xhr-Objektelement konfiguriert werden.
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 ) {
// 超时处理
}
} )
Passen Sie den Erfolgs- und Fehlerbereich des HTTP-Statuscodes an
import axios from 'axios'
axios . defaults . validateStatus = status => status >= 200 && status < 300 ;
In der Standardkonfiguration sind die standardmäßigen HTTP-Statuscode-Überprüfungsregeln definiert, sodass das Anpassen von validateStatus
hier tatsächlich eine Überschreibung der Methode darstellt.
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus : function validateStatus ( status ) {
return status >= 200 && status < 300 ;
} ,
// ...
}
Wann hat Axios mit der Validierung von HTTP-Statuscodes begonnen?
// /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
) ) ;
}
} ;
Im Axios-Projekt gibt es viele clevere Möglichkeiten, JS zu verwenden, z. B. die serielle Operation von Versprechen (man kann natürlich auch sagen, dass dies auf den Verarbeitungsmethoden vieler asynchroner Middleware basiert), die es uns ermöglichen, die verschiedenen Dinge einfach zu handhaben Kontrollieren Sie den Ablauf jeder Verarbeitungsmethode, z. B. die Datenverarbeitung vor und nach Anfragen, und ersparen Sie Programmierern das wiederholte Schreiben von JSON.xxx für Projekte Verwenden von Knoten Es ist zweifellos ausgezeichnet.
Kurz gesagt, dieser Star, der auf Github 42.000+ erreichen kann (Stand: 27.05.2018), ist keineswegs eine Schätzung seiner Stärke und es lohnt sich, mit ihm zu reden!