axios は、Promise に基づいた http リクエスト ライブラリであり、ブラウザおよび node.js で使用できます。現在、github に 42,000 個のスターが付いています。
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主类
│ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js # 拦截器构造函数
│ │ └── settle.js # 根据http响应状态,改变Promise的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现http适配器
│ │ └── xhr.js # 实现xhr适配器
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # 默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件
注: 確認する必要があるコードは/lib/
ディレクトリ内のすべてのファイルであるため、 /lib/
の下で次のすべてのファイル パスを検索します。
インターセプターインターセプター
(Promiseベースのミドルウェアの役割なので、ミドルウェアに詳しい方なら理解しやすいと思います)
インターセプターは、リクエスト インターセプターとレスポンス インターセプターに分けられます。名前が示すように、リクエスト インターセプター ( interceptors.request
) は、各または指定された http リクエストをインターセプトでき、応答インターセプター ( interceptors.response
) は毎回使用できます。複数の http リクエストの後、各 http リクエストまたは指定された http リクエストをインターセプトすると、返された結果項目を変更できます。
ここでは最初に簡単に説明し、その後でリクエストのレスポンスをインターセプトし、リクエストのパラメータとレスポンスのデータを変更する方法について詳しく紹介します。
データコンバーター (オブジェクトを JSON 文字列に変換するなど、実際にデータを変換します)
データ コンバーターは、リクエスト コンバーターとレスポンス コンバーターに分けられます。名前が示すように、リクエスト コンバーター ( transformRequest
) はリクエストの前のデータ変換を指し、レスポンス コンバーター ( transformResponse
) は主にリクエスト応答後のレスポンス本体のデータ変換を実行します。
http リクエスト アダプター (実際にはメソッド)
axios プロジェクトでは、http リクエスト アダプターは主に XHR と http の 2 種類を指します。 XHR の中核はブラウザ側の XMLHttpRequest オブジェクトであり、http の中核はノードの http[s].request メソッドです。
もちろん、axios は config を介してアダプター インターフェイスを構成することもユーザーに任せます。ただし、通常の状況では、これら 2 つのアダプターはブラウザーからサーバーへの要求、またはノードの http クライアントからサーバーへの要求を満たすことができます。
この共有は主に XHR に焦点を当てています。
config 構成アイテム (実際にはオブジェクト)
ここで説明する config は、実際にはプロジェクト内の変数名 config とは呼ばれません。この名前は、皆さんが理解しやすいように、その目的に基づいて付けた名前です。
axios プロジェクトでは、config を設定または読み取るときに、いくつかの場所ではそれをdefaults
( /lib/defaults.js
) と呼びます。これはデフォルトの設定項目であり、いくつかの場所ではそれをconfig
呼びますAxios.prototype.request
のパラメーターなど)。別の例は、 xhrAdapter
http リクエスト アダプター メソッドのパラメーターです。
config は axios プロジェクトの非常に重要なリンクであり、ユーザーと axios プロジェクト間の「通信」の主要なブリッジです。
(注: 最初はこのセクションをスキップし、必要に応じて後で戻って確認することができます)
プロジェクトの随所で使われているメソッドを簡単に紹介します。
bind ( fn , context ) ;
実装の効果はFunction.prototype.bind
メソッドと同じです: fn.bind(context)
var utils = require ( './utils' ) ;
var forEach = utils . forEach ;
// 数组
utils . forEach ( [ ] , ( value , index , array ) => { } )
// 对象
utils . forEach ( { } , ( value , key , object ) => { } )
var utils = require ( './utils' ) ;
var merge = utils . merge ;
var obj1 = {
a : 1 ,
b : {
bb : 11 ,
bbb : 111 ,
}
} ;
var obj2 = {
a : 2 ,
b : {
bb : 22 ,
}
} ;
var mergedObj = merge ( obj1 , obj2 ) ;
mergedObj オブジェクトは次のとおりです。
{
a : 2 ,
b : {
bb : 22 ,
bbb : 111
}
}
var utils = require ( './utils' ) ;
var extend = utils . extend ;
var context = {
a : 4 ,
} ;
var target = {
k : 'k1' ,
fn ( ) {
console . log ( this . a + 1 )
}
} ;
var source = {
k : 'k2' ,
fn ( ) {
console . log ( this . a - 1 )
}
} ;
let extendObj = extend ( target , source , context ) ;
extendObj オブジェクトは次のとおりです。
{
k : 'k2' ,
fn : source . fn . bind ( context ) ,
}
extendObj.fn();
を実行し、 3
出力します
// 首先将axios包引进来
import axios from 'axios'
最初の使用方法: axios(option)
axios ( {
url ,
method ,
headers ,
} )
2 番目の使用方法: axios(url[, option])
axios ( url , {
method ,
headers ,
} )
3 番目の使用方法 ( get、delete
などのメソッド): axios[method](url[, option])
axios . get ( url , {
headers ,
} )
4 番目の使用方法 ( post、put
などのメソッド): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
5 番目の使用方法: axios.request(option)
axios . request ( {
url ,
method ,
headers ,
} )
axios プロジェクトのエントリ ファイルとして、まずaxios.js
axios のさまざまな使用方法を実現できるコアは、 createInstance
メソッドです。
// /lib/axios.js
function createInstance ( defaultConfig ) {
// 创建一个Axios实例
var context = new Axios ( defaultConfig ) ;
// 以下代码也可以这样实现:var instance = Axios.prototype.request.bind(context);
// 这样instance就指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用
// Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
var instance = bind ( Axios . prototype . request , context ) ;
// 把Axios.prototype上的方法扩展到instance对象上,
// 这样 instance 就有了 get、post、put等方法
// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
utils . extend ( instance , Axios . prototype , context ) ;
// 把context对象上的自身属性和方法扩展到instance上
// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
// 这样,instance 就有了 defaults、interceptors 属性。(这两个属性后面我们会介绍)
utils . extend ( instance , context ) ;
return instance ;
}
// 接收默认配置项作为参数(后面会介绍配置项),创建一个Axios实例,最终会被作为对象导出
var axios = createInstance ( defaults ) ;
実際、上記のコードは複雑に見えますが、 createInstance
最終的にAxios.prototype.request
指す関数を取得することを望んでおり、これらのメソッドのコンテキストはAxios.prototype
にあります。同じオブジェクトを指します。
それでは、 Axios、Axios.prototype.request
のソース コードを見てみましょう。
Axios
Axios
パッケージのコアです。他のメソッドはAxios
コンテンツの拡張です。さまざまなAxios
呼び出しメソッドは、最終的に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
} ) ) ;
} ;
} ) ;
上記のコードを通じて、 axios()、axios.get()、axios.post()
などのさまざまな方法で http リクエストを開始できます。
一般に、プロジェクトはデフォルトのエクスポートされた axios インスタンスを使用して要求を満たすことができます。要求が満たされない場合は、新しい axios インスタンスも作成する必要があります。次のコードを参照してください。
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
axios を使用する方法がこれほどたくさんある理由について説明した後、次のような疑問が浮かぶかもしれません。axios を使用する場合、 get
メソッドまたはpost
メソッドに関係なく、最終的にはAxios.prototype.request
メソッドが呼び出されます。このメソッドは、リクエストを送信する構成設定はどうなっているのかによって異なります。
Axios.prototype.request
について話し始める前に、まずユーザー構成の構成が axios プロジェクトでどのように機能するかを見てみましょう。
ここで説明するconfig
、プロジェクト全体にわたる構成アイテム オブジェクトを指します。このオブジェクトを通じて、次のことを設定できます。
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
axios
のほぼすべての機能がこのオブジェクトを通じて設定および提供されていることがわかります。このオブジェクトはaxios
プロジェクト内の通信ブリッジであるだけでなく、ユーザーとaxios
間の通信ブリッジでもあります。
まず、ユーザーが構成項目を定義する方法を見てみましょう。
import axios from 'axios'
// 第1种:直接修改Axios实例上defaults属性,主要用来设置通用配置
axios . defaults [ configName ] = value ;
// 第2种:发起请求时最终会调用Axios.prototype.request方法,然后传入配置项,主要用来设置“个例”配置
axios ( {
url ,
method ,
headers ,
} )
// 第3种:新建一个Axios实例,传入配置项,此处设置的是通用配置
let newAxiosInstance = axios . create ( {
[ configName ] : value ,
} )
Axios.prototype.request
メソッドのコード行を見てください: ( /lib/core/Axios.js
- 行 35)
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
デフォルト設定オブジェクトのdefaults
( /lib/defaults.js
)、Axios インスタンス属性this.defaults
、およびrequest
パラメータconfig
ここにマージされていることがわかります。
これから、複数の構成の優先順位は低位から高位まで次のようになります。 —> デフォルト構成オブジェクトdefaults
( /lib/defaults.js
)
—> {メソッド: 'get' }
—> Axios インスタンス プロパティthis.defaults
—> request
パラメータconfig
質問が残っていますdefaults
とthis.defaults
の設定が同じになるのはどのような場合でしょうか、また異なるのはどのような場合ですか?
ここまでは、複数の場所merge
後にconfig
オブジェクトを取得しましたが、このオブジェクトはプロジェクト内でどのように転送されるのでしょうか?
Axios . prototype . request = function request ( config ) {
// ...
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
var chain = [ dispatchRequest , undefined ] ;
// 将config对象当作参数传给Primise.resolve方法
var promise = Promise . resolve ( config ) ;
// ...省略代码
while ( chain . length ) {
// config会按序通过 请求拦截器 - dispatchRequest方法 - 响应拦截器
// 关于拦截器 和 dispatchRequest方法,下面会作为一个专门的小节来介绍。
promise = promise . then ( chain . shift ( ) , chain . shift ( ) ) ;
}
return promise ;
} ;
この時点で、 config
その伝説的な寿命を終えました-_-
次のセクションでは、ハイライトであるAxios.prototype.request
について説明します。
ここのコードは比較的複雑で、一部のメソッドはそれを理解するためにソースまで遡る必要があるため、関連するインターセプターと [ dispatchRequest
] については、後で簡単に理解するだけで済みます。 。
chain
配列は、インターセプター メソッドとdispatchRequest
メソッドを保持するために使用されます。コールバック関数は、Promise を通じてchain
配列から順番に取り出され、最後に、処理された新しい Promise がAxios.prototype.request
メソッドで返されます。そして、応答またはエラーが送信されます。これがAxios.prototype.request
の使命です。
ソースコードを表示:
// /lib/core/Axios.js
Axios . prototype . request = function request ( config ) {
// ...
var chain = [ dispatchRequest , undefined ] ;
var promise = Promise . resolve ( config ) ;
this . interceptors . request . forEach ( function unshiftRequestInterceptors ( interceptor ) {
chain . unshift ( interceptor . fulfilled , interceptor . rejected ) ;
} ) ;
this . interceptors . response . forEach ( function pushResponseInterceptors ( interceptor ) {
chain . push ( interceptor . fulfilled , interceptor . rejected ) ;
} ) ;
while ( chain . length ) {
promise = promise . then ( chain . shift ( ) , chain . shift ( ) ) ;
}
return promise ;
} ;
この時点で、あなたはインターセプターについて興味を持っているはずです。このインターセプターとは何なのか、次のセクションで見てみましょう。
// 添加请求拦截器
const myRequestInterceptor = axios . interceptors . request . use ( config => {
// 在发送http请求之前做些什么
return config ; // 有且必须有一个config对象被返回
} , error => {
// 对请求错误做些什么
return Promise . reject ( error ) ;
} ) ;
// 添加响应拦截器
axios . interceptors . response . use ( response => {
// 对响应数据做点什么
return response ; // 有且必须有一个response对象被返回
} , error => {
// 对响应错误做点什么
return Promise . reject ( error ) ;
} ) ;
// 移除某次拦截器
axios . interceptors . request . eject ( myRequestInterceptor ) ;
axios . interceptors . request . use ( config => config , error => {
// 是否可以直接 return error ?
return Promise . reject ( error ) ;
} ) ;
new People ( 'whr' ) . sleep ( 3000 ) . eat ( 'apple' ) . sleep ( 5000 ) . eat ( 'durian' ) ;
// 打印结果
// (等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'
インターセプターについては、用語集のセクションで簡単に説明しています。
各 axios インスタンスにはinterceptors
インスタンス属性があり、 interceptors
オブジェクトにはrequest
とresponse
2 つの属性があります。
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
どちらのプロパティもInterceptorManager
インスタンスであり、このInterceptorManager
コンストラクターはインターセプターの管理に使用されます。
まず、 InterceptorManager
コンストラクターを見てみましょう。
InterceptorManager
コンストラクターは、インターセプターを実装するために使用されます。このコンストラクターのプロトタイプには、use、eject、forEach の 3 つのメソッドがあります。 ソース コードに関しては、実際には比較的単純で、コンストラクターのハンドラー インスタンス属性を操作するために使用されます。
// /lib/core/InterceptorManager.js
function InterceptorManager ( ) {
this . handlers = [ ] ; // 存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。
}
// 往拦截器里添加拦截方法
InterceptorManager . prototype . use = function use ( fulfilled , rejected ) {
this . handlers . push ( {
fulfilled : fulfilled ,
rejected : rejected
} ) ;
return this . handlers . length - 1 ;
} ;
// 用来注销指定的拦截器
InterceptorManager . prototype . eject = function eject ( id ) {
if ( this . handlers [ id ] ) {
this . handlers [ id ] = null ;
}
} ;
// 遍历this.handlers,并将this.handlers里的每一项作为参数传给fn执行
InterceptorManager . prototype . forEach = function forEach ( fn ) {
utils . forEach ( this . handlers , function forEachHandler ( h ) {
if ( h !== null ) {
fn ( h ) ;
}
} ) ;
} ;
それでは、 axios.interceptors.request.use
を通じてインターセプターを追加する場合、axios はどのように内部的にこれらのインターセプターがリクエストの前後に必要なデータを取得できるようにするのでしょうか?
まずコードを見てみましょう:
// /lib/core/Axios.js
Axios . prototype . request = function request ( config ) {
// ...
var chain = [ dispatchRequest , undefined ] ;
// 初始化一个promise对象,状态为resolved,接收到的参数为config对象
var promise = Promise . resolve ( config ) ;
// 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
this . interceptors . request . forEach ( function unshiftRequestInterceptors ( interceptor ) {
chain . unshift ( interceptor . fulfilled , interceptor . rejected ) ;
} ) ;
this . interceptors . response . forEach ( function pushResponseInterceptors ( interceptor ) {
chain . push ( interceptor . fulfilled , interceptor . rejected ) ;
} ) ;
// 添加了拦截器后的chain数组大概会是这样的:
// [
// requestFulfilledFn, requestRejectedFn, ...,
// dispatchRequest, undefined,
// responseFulfilledFn, responseRejectedFn, ....,
// ]
// 只要chain数组长度不为0,就一直执行while循环
while ( chain . length ) {
// 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
// 每次执行while循环,从chain数组里按序取出两项,并分别作为promise.then方法的第一个和第二个参数
// 按照我们使用InterceptorManager.prototype.use添加拦截器的规则,
// 正好每次添加的就是我们通过InterceptorManager.prototype.use方法添加的成功和失败回调
// 通过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法,
// 对于请求拦截器:
// 从拦截器数组按序读到后是通过unshift方法往chain数组数里添加的,又通过shift方法从chain数组里取出的,
// 所以得出结论:对于请求拦截器,先添加的拦截器会后执行
// 对于响应拦截器:
// 从拦截器数组按序读到后是通过push方法往chain数组里添加的,又通过shift方法从chain数组里取出的,
// 所以得出结论:对于响应拦截器,添加的拦截器先执行
// 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,
// 而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,
// 所以通过promise实现链式调用时,每个请求拦截器的fulfilled函数都会接收到一个config对象
// 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是我们的请求方法)请求到的数据(也就是response对象),
// 而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,
// 所以通过promise实现链式调用时,每个响应拦截器的fulfilled函数都会接收到一个response对象
// 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,
// 所以dispatchRequest抛出的错误才会被响应拦截器接收到。
// 因为axios是通过promise实现的链式调用,所以我们可以在拦截器里进行异步操作,
// 而拦截器的执行顺序还是会按照我们上面说的顺序执行,
// 也就是 dispatchRequest 方法一定会等待所有的请求拦截器执行完后再开始执行,
// 响应拦截器一定会等待 dispatchRequest 执行完后再开始执行。
promise = promise . then ( chain . shift ( ) , chain . shift ( ) ) ;
}
return promise ;
} ;
これで、インターセプターとは何か、そしてそれがAxios.prototype.request
メソッドでどのように機能するかについて明確に理解できたはずです。では、「ミッドストリーム位置」のdispatchRequest
どのようにhttpリクエストを送信するのでしょうか。
dispatchRequest は主に 3 つのことを行います: 1. 構成オブジェクトを取得し、それを http 要求アダプターに渡す前に構成に対して最終処理を実行します。 2. http 要求アダプターは、構成構成に基づいて要求を開始します。 3. http 要求アダプターの後。成功すればリクエスト完了。ヘッダー、データ、config.transformResponseを元にデータ変換後のレスポンスを取得し(transformResponseについては後述のデータコンバーターで説明します)、返します。
// /lib/core/dispatchRequest.js
module . exports = function dispatchRequest ( config ) {
throwIfCancellationRequested ( config ) ;
// Support baseURL config
if ( config . baseURL && ! isAbsoluteURL ( config . url ) ) {
config . url = combineURLs ( config . baseURL , config . url ) ;
}
// Ensure headers exist
config . headers = config . headers || { } ;
// 对请求data进行转换
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
// 对header进行合并处理
config . headers = utils . merge (
config . headers . common || { } ,
config . headers [ config . method ] || { } ,
config . headers || { }
) ;
// 删除header属性里无用的属性
utils . forEach (
[ 'delete' , 'get' , 'head' , 'post' , 'put' , 'patch' , 'common' ] ,
function cleanHeaderConfig ( method ) {
delete config . headers [ method ] ;
}
) ;
// http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,
// 不过大部分时候,axios提供的默认适配器是能够满足我们的
var adapter = config . adapter || defaults . adapter ;
return adapter ( config ) . then ( /**/ ) ;
} ;
さて、これを見て、次は整理してみましょう。Axios は Promise をどのように使用して、XHR に基づいた非同期ブリッジを構築するのでしょうか?
axios は Promise を通じてどのように非同期処理を実行しますか?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
まず、axios プロジェクトで http リクエストが完了した後にユーザーに到達するまでのシーケンス フローを簡単に理解するための図を作成します。
axios がさまざまな方法で使用できる理由から、ユーザーが axios をどのように呼び出しても、最終的にはAxios.prototype.request
メソッドを呼び出し、最終的に Promise オブジェクトを返すことになることがわかります。
Axios . prototype . request = function request ( config ) {
// ...
var chain = [ dispatchRequest , undefined ] ;
// 将config对象当作参数传给Primise.resolve方法
var promise = Promise . resolve ( config ) ;
while ( chain . length ) {
promise = promise . then ( chain . shift ( ) , chain . shift ( ) ) ;
}
return promise ;
} ;
Axios.prototype.request
メソッドはdispatchRequest
メソッドを呼び出し、 dispatchRequest
メソッドはxhrAdapter
メソッドを呼び出します。xhrAdapter xhrAdapter
は Promise オブジェクトを返します。
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
xhrAdapter
の XHR がリクエストを正常に送信すると、Promise オブジェクトのresolve
メソッドが実行され、リクエストされたデータが渡されます。それ以外の場合は、 reject
メソッドが実行され、エラー情報がパラメータとして渡されます。
// /lib/adapters/xhr.js
var request = new XMLHttpRequest ( ) ;
var loadEvent = 'onreadystatechange' ;
request [ loadEvent ] = function handleLoad ( ) {
// ...
// 往下走有settle的源码
settle ( resolve , reject , response ) ;
// ...
} ;
request . onerror = function handleError ( ) {
reject ( /**/ ) ;
request = null ;
} ;
request . ontimeout = function handleTimeout ( ) {
reject ( /**/ ) ;
request = null ;
} ;
サーバーから返された結果が検証に合格したかどうかを確認します。
// /lib/core/settle.js
function settle ( resolve , reject , response ) {
var validateStatus = response . config . validateStatus ;
if ( ! response . status || ! validateStatus || validateStatus ( response . status ) ) {
resolve ( response ) ;
} else {
reject ( /**/ ) ;
}
} ;
dispatchRequest
メソッドに戻り、まずxhrAdapter
メソッドによって返された Promise オブジェクトを取得し、次に.then
メソッドを通じてxhrAdapter
によって返された Promise オブジェクトの成功または失敗の結果を処理します。成功した場合は、処理されたresponse
が返されます。失敗すると、ステータスrejected
た Promise オブジェクトが返されます。
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
したがって、この時点で、ユーザーがaxios()
メソッドを呼び出すと、業務処理のために Promise の.then
または.catch
を直接呼び出すことができます。
振り返ってみると、 dispatchRequest
導入したときにデータ変換について話しましたが、axios はハイライトとしてデータ変換を正式に導入しました。それでは、axios を使用するとデータ変換にどのような効果があるでしょうか?
import axios from 'axios'
// 往现有的请求转换器里增加转换方法
axios . defaults . transformRequest . push ( ( data , headers ) => {
// ...处理data
return data ;
} ) ;
// 重写请求转换器
axios . defaults . transformRequest = [ ( data , headers ) => {
// ...处理data
return data ;
} ] ;
// 往现有的响应转换器里增加转换方法
axios . defaults . transformResponse . push ( ( data , headers ) => {
// ...处理data
return data ;
} ) ;
// 重写响应转换器
axios . defaults . transformResponse = [ ( data , headers ) => {
// ...处理data
return data ;
} ] ;
import axios from 'axios'
// 往已经存在的转换器里增加转换方法
axios . get ( url , {
// ...
transformRequest : [
... axios . defaults . transformRequest , // 去掉这行代码就等于重写请求转换器了
( data , headers ) => {
// ...处理data
return data ;
}
] ,
transformResponse : [
... axios . defaults . transformResponse , // 去掉这行代码就等于重写响应转换器了
( data , headers ) => {
// ...处理data
return data ;
}
] ,
} )
リクエストコンバーターとレスポンスコンバーターは、デフォルトのdefaults
設定項目でカスタマイズされています。ソースコードを見てください。
// /lib/defaults.js
var defaults = {
transformRequest : [ function transformRequest ( data , headers ) {
normalizeHeaderName ( headers , 'Content-Type' ) ;
// ...
if ( utils . isArrayBufferView ( data ) ) {
return data . buffer ;
}
if ( utils . isURLSearchParams ( data ) ) {
setContentTypeIfUnset ( headers , 'application/x-www-form-urlencoded;charset=utf-8' ) ;
return data . toString ( ) ;
}
if ( utils . isObject ( data ) ) {
setContentTypeIfUnset ( headers , 'application/json;charset=utf-8' ) ;
return JSON . stringify ( data ) ;
}
return data ;
} ] ,
transformResponse : [ function transformResponse ( data ) {
if ( typeof data === 'string' ) {
try {
data = JSON . parse ( data ) ;
} catch ( e ) { /* Ignore */ }
}
return data ;
} ] ,
} ;
では、axios プロジェクトのどこでコンバーターが使用されているのでしょうか?
リクエスト コンバータは、http リクエストの前に使用され、リクエスト データを処理した後、http リクエスト アダプタに渡されて使用されます。
// /lib/core/dispatchRequest.js
function dispatchRequest ( config ) {
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
return adapter ( config ) . then ( /* ... */ ) ;
} ;
このtransformData
は、主にコンバーター配列を走査し、各コンバーターを個別に実行し、データとヘッダーのパラメーターに従って新しいデータを返します。
// /lib/core/transformData.js
function transformData ( data , headers , fns ) {
utils . forEach ( fns , function transform ( fn ) {
data = fn ( data , headers ) ;
} ) ;
return data ;
} ;
レスポンスコンバータは、httpリクエスト完了後にhttpリクエストアダプタの戻り値に基づいてデータ変換処理を行うために使用されます。
// /lib/core/dispatchRequest.js
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
response . data = transformData (
response . data ,
response . headers ,
config . transformResponse
) ;
return response ;
} , function onAdapterRejection ( reason ) {
if ( ! isCancel ( reason ) ) {
// ...
if ( reason && reason . response ) {
reason . response . data = transformData (
reason . response . data ,
reason . response . headers ,
config . transformResponse
) ;
}
}
return Promise . reject ( reason ) ;
} ) ;
インターセプターはリクエスト データとレスポンス データを変換する必要性も認識しますが、作成者の設計と包括的なコードによれば、リクエスト中にインターセプターが主に構成設定項目の変更を担当し、データ コンバーターがインターセプターは主に、オブジェクトの変換など、リクエスト本文の変換を担当します。文字列の応答をリクエストした後、データコンバーターは、文字列をオブジェクトに変換するなど、 response
本文を処理します。
Axios は、独立したハイライトとして「JSON データへの自動変換」を公式に導入しています。では、データ コンバーターはどのようにしてこの機能を実現するのでしょうか。 実はとても簡単なので見てみましょう。
デフォルトでは、axios は受信データ オブジェクトを JSON 文字列に自動的にシリアル化し、応答データ内の JSON 文字列を JavaScript オブジェクトに変換します。
// 请求时,将data数据转换为JSON 字符串
// /lib/defaults.js
transformRequest: [ function transformRequest ( data , headers ) {
// ...
if ( utils . isObject ( data ) ) {
setContentTypeIfUnset ( headers , 'application/json;charset=utf-8' ) ;
return JSON . stringify ( data ) ;
}
return data ;
} ]
// 得到响应后,将请求到的数据转换为JSON对象
// /lib/defaults.js
transformResponse: [ function transformResponse ( data ) {
if ( typeof data === 'string' ) {
try {
data = JSON . parse ( data ) ;
} catch ( e ) { /* Ignore */ }
}
return data ;
} ]
ここまでで、axios プロジェクトの操作プロセスが紹介されました。Ren と Du の 2 つのチャンネルはすでに開設されましたか? 次に、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 ;
} ) ;
}
キャンセル関数の核心は、CancelToken のthis.promise = new Promise(resolve => resolvePromise = resolve)
を介してインスタンス属性のpromise
を取得することです。この時点で、 promise
のステータスは/lib/adapters/xhr.js
のこの属性によってpending
なります。 /lib/adapters/xhr.js
引き続き、ファイル内のこのpromise
インスタンスに.then
メソッドを追加します ( xhr.js
ファイルの 159 行目config.cancelToken.promise.then(message => request.abort())
);
CancelToken
の外側では、 executor
パラメータを使用してcancel
メソッドの制御を取得します。このようにして、 cancel
メソッドが実行されると、インスタンスのpromise
属性のステータスがrejected
され、 request.abort()
が実行されます。 request.abort()
リクエストをキャンセルするメソッド。
上記の 2 番目の記述方法は、最初の記述方法の改良版と見なすことができます。これは、リクエスト キャンセル メソッドが現在のリクエスト メソッド以外で使用されることが多いためです。たとえば、2 つのリクエスト A と B を送信するときに、リクエスト B が送信される場合です。成功した場合、リクエスト A をキャンセルします。
// 第1种写法:
let source ;
axios . get ( Aurl , {
cancelToken : new axios . CancelToken ( cancel => {
source = cancel ;
} )
} ) ;
axios . get ( Burl )
. then ( ( ) => source ( 'B请求成功了' ) ) ;
// 第2种写法:
const CancelToken = axios . CancelToken ;
const source = CancelToken . source ( ) ;
axios . get ( Aurl , {
cancelToken : source . token
} ) ;
axios . get ( Burl )
. then ( ( ) => source . cancel ( 'B请求成功了' ) ) ;
相対的に言えば、私は最初の書き方の方が好きです。なぜなら、2 番目の書き方は隠されすぎていて、最初の方法ほど直観的で理解しにくいからです。
/lib/adapters/xhr.js ファイルでは、onCanceled メソッドのパラメータを message と呼ぶべきではないでしょうか。なぜそれが cancel と呼ばれるのでしょうか?
/lib/adapters/xhr.js ファイルの onCanceled メソッドで、構成情報も拒否で渡す必要があります。
import axios from 'axios'
axios . defaults . withCredentials = true ;
ユーザー設定の設定がどのように機能するかに関するセクションで axios プロジェクトの設定の転送プロセスをすでに紹介しました。このことから、 axios.defaults.withCredentials = true
によって作成した設定は/lib/adapters/xhr.js
にあると結論付けることができます。 /lib/adapters/xhr.js
。
var request = new XMLHttpRequest ( ) ;
// /lib/adapters/xhr.js
if ( config . withCredentials ) {
request . withCredentials = true ;
}
import axios from 'axios'
axios . defaults . timeout = 3000 ;
// /adapters/xhr.js
request . timeout = config . timeout ;
// /adapters/xhr.js
// 通过createError方法,将错误信息合为一个字符串
request . ontimeout = function handleTimeout ( ) {
reject ( createError ( 'timeout of ' + config . timeout + 'ms exceeded' ,
config , 'ECONNABORTED' , request ) ) ;
} ;
axios ( ) . catch ( error => {
const { message } = error ;
if ( message . indexOf ( 'timeout' ) > - 1 ) {
// 超时处理
}
} )
httpステータスコードの成功および失敗の範囲をカスタマイズする
import axios from 'axios'
axios . defaults . validateStatus = status => status >= 200 && status < 300 ;
デフォルトの構成では、デフォルトの http ステータス コード検証ルールが定義されているため、 validateStatus
のカスタマイズは実際にはここでのメソッドのオーバーライドになります。
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus : function validateStatus ( status ) {
return status >= 200 && status < 300 ;
} ,
// ...
}
axios はいつから http ステータス コードの検証を開始しましたか?
// /lib/adapters/xhr.js
var request = new XMLHttpRequest ( ) ;
var loadEvent = 'onreadystatechange' ;
// /lib/adapters/xhr.js
// 每当 readyState 改变时,就会触发 onreadystatechange 事件
request [ loadEvent ] = function handleLoad ( ) {
if ( ! request || ( request . readyState !== 4 && ! xDomain ) ) {
return ;
}
// ...省略代码
var response = {
// ...
// IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
status : request . status === 1223 ? 204 : request . status ,
config : config ,
} ;
settle ( resolve , reject , response ) ;
// ...省略代码
}
// /lib/core/settle.js
function settle ( resolve , reject , response ) {
// 如果我们往上捣一捣就会发现,config对象的validateStatus就是我们自定义的validateStatus方法或默认的validateStatus方法
var validateStatus = response . config . validateStatus ;
// validateStatus验证通过,就会触发resolve方法
if ( ! response . status || ! validateStatus || validateStatus ( response . status ) ) {
resolve ( response ) ;
} else {
reject ( createError (
'Request failed with status code ' + response . status ,
response . config ,
null ,
response . request ,
response
) ) ;
}
} ;
axios プロジェクトでは、Promise のシリアル操作など (もちろん多くの非同期ミドルウェアの処理方法をベースにしているとも言えます) など、JS の賢い使い方がたくさんあり、さまざまな処理を簡単に行うことができます。リクエストの前後のプロセスを制御し、リクエストの前後のデータ処理などの多くの実用的な最適化を行い、プロジェクトで JSON.xxx を何度も作成する手間を省きます。ノードの使用 間違いなく優れています。
つまり、github で 42,000 以上を獲得できるこのスター (2018.05.27 時点) は決して彼の強さを見積もるものではなく、話す価値があります。