axios adalah perpustakaan permintaan http berdasarkan Promise, yang dapat digunakan di browser dan node.js. Saat ini memiliki 42 ribu bintang di 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 # 入口文件
Catatan: Karena kode yang perlu kita lihat adalah semua file di direktori /lib/
, kita akan mencari di bawah /lib/
untuk semua jalur file berikut.
pencegat, pencegat
(Jika Anda sudah familiar dengan middleware, akan mudah memahaminya, karena berperan sebagai middleware berbasis janji)
Pencegat dibagi menjadi pencegat permintaan dan pencegat respons. Sesuai dengan namanya: Pencegat permintaan ( interceptors.request
) dapat mencegat setiap permintaan http yang ditentukan, dan dapat mengubah item konfigurasi. Pencegat respons ( interceptors.response
) dapat digunakan setiap saat. Setelah beberapa permintaan http, intersepsi setiap atau permintaan http tertentu, dan item hasil yang dikembalikan dapat dimodifikasi.
Berikut penjelasan singkatnya terlebih dahulu, dan selanjutnya kami akan memberikan pengenalan mendetail tentang cara mencegat respons permintaan dan mengubah parameter permintaan dan data respons.
Pengonversi data (sebenarnya mengonversi data, seperti mengonversi objek menjadi string JSON)
Pengonversi data dibagi menjadi pengonversi permintaan dan pengonversi respons. Sesuai dengan namanya: pengonversi permintaan ( transformRequest
) mengacu pada konversi data sebelum permintaan, dan pengonversi respons ( transformResponse
) terutama melakukan konversi data pada badan respons setelah respons permintaan.
adaptor permintaan http (sebenarnya sebuah metode)
Dalam proyek axios, adaptor permintaan http terutama mengacu pada dua jenis: XHR dan http. Inti dari XHR adalah objek XMLHttpRequest di sisi browser, dan inti dari http adalah metode http[s].request dari node.
Tentu saja, axios juga menyerahkan kepada pengguna untuk mengkonfigurasi antarmuka adaptor melalui konfigurasi. Namun, dalam keadaan normal, kedua adaptor ini dapat memenuhi permintaan dari browser ke server atau dari klien http node ke server.
Berbagi ini terutama berfokus pada XHR.
config item konfigurasi (sebenarnya sebuah objek)
Konfigurasi yang kita bicarakan di sini sebenarnya tidak disebut dengan nama variabel config dalam proyek. Nama ini adalah nama yang saya berikan berdasarkan tujuannya untuk memudahkan pemahaman semua orang.
Dalam proyek axios, saat menyetelmembaca konfigurasi, beberapa tempat menyebutnya defaults
( /lib/defaults.js
), ini adalah item konfigurasi default, dan beberapa tempat menyebutnya config
, seperti parameter Axios.prototype.request
, Contoh lainnya adalah parameter metode adaptor permintaan http xhrAdapter
.
config merupakan link yang sangat penting dalam proyek axios, dan merupakan jembatan utama “komunikasi” antara pengguna dan proyek axios.
(Catatan: Anda dapat melewati bagian ini terlebih dahulu dan kembali lagi untuk memeriksanya nanti jika Anda memerlukannya)
Ada beberapa metode yang digunakan di banyak tempat dalam proyek ini. Mari kita perkenalkan secara singkat metode ini.
bind ( fn , context ) ;
Efek implementasinya sama dengan metode 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 ) ;
Objek mergeObj adalah:
{
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 ) ;
Objek extendObj adalah:
{
k : 'k2' ,
fn : source . fn . bind ( context ) ,
}
Jalankan extendObj.fn();
cetak 3
// 首先将axios包引进来
import axios from 'axios'
Cara pertama menggunakan: axios(option)
axios ( {
url ,
method ,
headers ,
} )
Cara kedua untuk menggunakan: axios(url[, option])
axios ( url , {
method ,
headers ,
} )
Metode penggunaan ketiga (untuk metode get、delete
dll.): axios[method](url[, option])
axios . get ( url , {
headers ,
} )
Metode penggunaan keempat (untuk metode post、put
dll): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
Cara kelima menggunakan: axios.request(option)
axios . request ( {
url ,
method ,
headers ,
} )
Sebagai file entri proyek axios, pertama-tama mari kita lihat kode sumber axios.js
Inti yang dapat mewujudkan berbagai cara menggunakan axios adalah metode 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 ) ;
Kode di atas tampaknya berbelit-belit. Faktanya, createInstance
pada akhirnya berharap untuk mendapatkan sebuah Fungsi. Fungsi ini menunjuk ke Axios.prototype.request
. Fungsi ini juga akan memiliki setiap metode di Axios.prototype
sebagai metode statis, dan konteks dari metode ini adalah menunjuk ke objek yang sama.
Jadi mari kita lihat kode sumber Axios、Axios.prototype.request
?
Axios
adalah inti dari paket axios. Contoh Axios
adalah aplikasi axios. Metode lain adalah ekstensi dari konten Axios
. Metode inti dari konstruktor Axios
adalah 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
} ) ) ;
} ;
} ) ;
Melalui kode di atas, kita dapat memulai permintaan http dengan berbagai cara: axios()、axios.get()、axios.post()
Secara umum, proyek dapat memenuhi permintaan dengan menggunakan instance axios yang diekspor secara default. Jika permintaan tidak terpenuhi, instance axios baru perlu dibuat. Paket axios juga mencadangkan antarmuka.
// /lib/axios.js - 31行
axios . Axios = Axios ;
axios . create = function create ( instanceConfig ) {
return createInstance ( utils . merge ( defaults , instanceConfig ) ) ;
} ;
Setelah membicarakan mengapa ada begitu banyak cara untuk menggunakan axios, Anda mungkin memiliki pertanyaan di benak Anda: Saat menggunakan axios, tidak peduli metode get
atau metode post
, metode Axios.prototype.request
pada akhirnya dipanggil metode ini bergantung pada konfigurasi kita. Bagaimana dengan konfigurasi config yang mengirimkan permintaan?
Sebelum kita mulai membicarakan Axios.prototype.request
, pertama-tama mari kita lihat cara kerja konfigurasi yang dikonfigurasi pengguna di proyek axios?
config
yang disebutkan di sini mengacu pada objek item konfigurasi di seluruh proyek. Melalui objek ini, Anda dapat mengatur:
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
Dapat ditemukan bahwa hampir semua fungsi axios
dikonfigurasi dan dikirimkan melalui objek ini, yang tidak hanya menjadi jembatan komunikasi dalam proyek axios
, tetapi juga jembatan komunikasi antara pengguna dan axios
.
Pertama, mari kita lihat bagaimana pengguna dapat menentukan item konfigurasi:
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 ,
} )
Lihatlah baris kode dalam metode Axios.prototype.request
: ( /lib/core/Axios.js
- baris 35)
config = utils . merge ( defaults , { method : 'get' } , this . defaults , config ) ;
Dapat ditemukan bahwa objek konfigurasi default defaults
( /lib/defaults.js
), atribut instance Axios this.defaults
, dan config
parameter request
digabungkan di sini.
Dari sini, prioritas beberapa konfigurasi dari rendah ke tinggi adalah: —> Default objek konfigurasi defaults
( /lib/defaults.js
)
—> { metode: 'dapatkan' }
—> Properti instance Axios this.defaults
-> request
config
parameter
Saya meninggalkan Anda dengan pertanyaan: Kapan konfigurasi defaults
dan this.defaults
sama, dan kapan berbeda?
Sejauh ini, kami telah memperoleh objek config
setelah merge
beberapa tempat, jadi bagaimana objek ini ditransfer dalam proyek?
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 ;
} ;
Pada titik ini, config
telah menyelesaikan kehidupan legendarisnya -_-
Bagian selanjutnya akan membahas tentang sorotan: Axios.prototype.request
Kode di sini relatif rumit, dan beberapa metode perlu ditelusuri kembali ke sumbernya untuk mengetahuinya, jadi Anda hanya perlu memiliki pemahaman sederhana tentang array rantai. Interseptor dan [ dispatchRequest
] yang terlibat akan diperkenalkan secara detail nanti .
Array chain
digunakan untuk menampung metode interseptor dan metode dispatchRequest
Axios.prototype.request
Fungsi panggilan balik diambil dari array chain
secara berurutan melalui janji dan dieksekusi satu per satu. dan Respons atau kesalahan dikirimkan. Ini adalah misi Axios.prototype.request
.
Lihat kode sumber:
// /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 ;
} ;
Sampai di sini, Anda pasti penasaran dengan pencegat itu. Apa sebenarnya pencegat itu? Mari kita cari tahu di bagian selanjutnya.
// 添加请求拦截器
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'
Mengenai interseptor, penjelasan singkatnya telah diberikan pada bagian Glosarium.
Setiap instance axios memiliki atribut instance interceptors
, dan terdapat dua atribut request
dan response
pada objek interceptors
.
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
Kedua properti tersebut merupakan instance InterceptorManager
, dan konstruktor InterceptorManager
ini digunakan untuk mengelola interseptor.
Pertama mari kita lihat konstruktor InterceptorManager
:
Konstruktor InterceptorManager
digunakan untuk mengimplementasikan interseptor. Ada tiga metode pada prototipe konstruktor ini: use, eject, dan forEach. Mengenai kode sumber, sebenarnya relatif sederhana dan digunakan untuk mengoperasikan atribut instance handler dari konstruktor.
// /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 ) ;
}
} ) ;
} ;
Jadi ketika kita menambahkan interseptor melalui axios.interceptors.request.use
, bagaimana axios secara internal memungkinkan interseptor ini mendapatkan data yang kita inginkan sebelum dan sesudah permintaan?
Mari kita lihat kodenya terlebih dahulu:
// /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 ;
} ;
Sekarang, Anda harus memiliki gambaran yang jelas tentang apa itu interseptor dan cara kerjanya dalam metode Axios.prototype.request
. Jadi, bagaimana cara dispatchRequest
di "posisi tengah" mengirim permintaan http?
DispatchRequest terutama melakukan tiga hal: 1. Mendapatkan objek konfigurasi dan melakukan pemrosesan akhir pada konfigurasi sebelum meneruskannya ke adaptor permintaan http; 2. Adaptor permintaan http memulai permintaan berdasarkan konfigurasi konfigurasi permintaan selesai, jika berhasil Kemudian dapatkan respon setelah konversi data berdasarkan header, data, dan config.transformResponse (tentang transformResponse, konverter data di bawah akan menjelaskannya) dan mengembalikannya.
// /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 ( /**/ ) ;
} ;
Oke, melihat hal ini, saatnya kita memilah: Bagaimana Axios menggunakan janji untuk membangun jembatan asinkron berbasis XHR?
Bagaimana aksio melakukan pemrosesan asinkron melalui Promise?
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
Pertama-tama mari kita ambil diagram untuk memahami secara singkat alur urutan menjangkau pengguna setelah permintaan http selesai dalam proyek aksio:
Melalui alasan axios dapat digunakan dalam banyak cara, kita tahu bahwa tidak peduli bagaimana pengguna memanggil axios, dia pada akhirnya akan memanggil metode Axios.prototype.request
, yang pada akhirnya mengembalikan objek 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 ;
} ;
Metode Axios.prototype.request
akan memanggil metode dispatchRequest
, dan metode dispatchRequest
akan memanggil metode xhrAdapter
. Metode xhrAdapter
mengembalikan objek Promise.
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
Setelah XHR di xhrAdapter
berhasil mengirimkan permintaan, ia akan mengeksekusi metode resolve
objek Promise dan meneruskan data yang diminta, jika tidak, ia akan menjalankan metode reject
dan meneruskan informasi kesalahan sebagai 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 ;
} ;
Verifikasi apakah hasil pengembalian dari server lolos verifikasi:
// /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 ( /**/ ) ;
}
} ;
Kembali ke metode dispatchRequest
, pertama-tama dapatkan objek Promise yang dikembalikan oleh response
xhrAdapter
, lalu proses hasil berhasil atau gagalnya objek Promise yang dikembalikan oleh xhrAdapter
melalui metode .then
gagal, lalu Mengembalikan objek Janji dengan status rejected
,
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
Jadi pada titik ini, ketika pengguna memanggil metode axios()
, dia dapat langsung memanggil .then
atau .catch
dari Promise untuk pemrosesan bisnis.
Melihat ke belakang, kita berbicara tentang konversi data ketika memperkenalkan dispatchRequest
, dan axios secara resmi memperkenalkan konversi data sebagai sorotan. Jadi, apa pengaruh konversi data dalam menggunakan 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 ;
}
] ,
} )
Konverter permintaan dan konverter respons telah dikustomisasi di item konfigurasi defaults
default. Lihatlah kode sumber:
// /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 ;
} ] ,
} ;
Jadi di proyek axios, konverternya digunakan dimana?
Konverter permintaan digunakan sebelum permintaan http. Konverter permintaan digunakan untuk memproses data permintaan, dan kemudian diteruskan ke adaptor permintaan http untuk digunakan.
// /lib/core/dispatchRequest.js
function dispatchRequest ( config ) {
config . data = transformData (
config . data ,
config . headers ,
config . transformRequest
) ;
return adapter ( config ) . then ( /* ... */ ) ;
} ;
Lihatlah kode metode transformData
. Metode ini terutama melintasi array konverter, mengeksekusi setiap konverter secara terpisah, dan mengembalikan data baru sesuai dengan parameter data dan header.
// /lib/core/transformData.js
function transformData ( data , headers , fns ) {
utils . forEach ( fns , function transform ( fn ) {
data = fn ( data , headers ) ;
} ) ;
return data ;
} ;
Konverter respons digunakan untuk melakukan pemrosesan konversi data berdasarkan nilai kembalian adaptor permintaan http setelah permintaan http selesai:
// /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 ) ;
} ) ;
Pencegat juga dapat menyadari kebutuhan untuk mengonversi data permintaan dan respons, tetapi menurut desain penulis dan kode komprehensif, dapat dilihat bahwa selama permintaan, pencegat terutama bertanggung jawab untuk memodifikasi item konfigurasi konfigurasi, dan pengonversi data adalah terutama bertanggung jawab untuk mengubah isi permintaan, seperti mengonversi objek. Setelah meminta respons untuk sebuah string, pencegat bisa mendapatkan response
. Konverter data terutama bertanggung jawab untuk memproses isi respons, seperti mengubah string menjadi objek.
Axios secara resmi memperkenalkan "konversi otomatis ke data JSON" sebagai sorotan independen. Jadi, bagaimana pengonversi data menyelesaikan fungsi ini? Sebenarnya sangat sederhana, mari kita lihat.
Secara default, axios akan secara otomatis membuat serial objek data yang masuk menjadi string JSON dan mengubah string JSON dalam data respons menjadi objek 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 ;
} ]
Pada titik ini, proses pengoperasian proyek axios telah diperkenalkan. Apakah Anda sudah membuka dua saluran Ren dan Du? Selanjutnya, mari kita lihat keterampilan berguna apa yang telah diberikan axios kepada kita.
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 ;
} ) ;
}
Inti dari fungsi pembatalan adalah mendapatkan atribut instance promise
melalui this.promise = new Promise /lib/adapters/xhr.js
this.promise = new Promise(resolve => resolvePromise = resolve)
di CancelToken Saat ini, status promise
adalah pending
/lib/adapters/xhr.js
Lanjutkan menambahkan metode .then
ke instance promise
ini di file (baris 159 dari file xhr.js
config.cancelToken.promise.then(message => request.abort())
);
Di luar CancelToken
, parameter executor
digunakan untuk mendapatkan kendali atas metode cancel
. Dengan cara ini, ketika metode cancel
dijalankan, status atribut promise
dari instance dapat diubah menjadi rejected
, sehingga mengeksekusi request.abort()
metode untuk membatalkan permintaan.
Cara penulisan yang kedua di atas bisa dilihat sebagai penyempurnaan dari cara penulisan yang pertama, karena sering kali metode pembatalan permintaan kita digunakan di luar metode permintaan yang sekarang, misalnya kita mengirim dua permintaan A dan B, ketika permintaan B berhasil, , batalkan permintaan 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请求成功了' ) ) ;
Secara relatif, saya lebih menyukai cara penulisan yang pertama, karena cara penulisan yang kedua terlalu tersembunyi dan tidak seintuitif dan mudah dipahami seperti cara pertama.
Di file /lib/adapters/xhr.js, bukankah parameter metode onCanceled harus disebut pesan?
Dalam file /lib/adapters/xhr.js, dalam metode onCanceled, informasi konfigurasi juga harus diteruskan sebagai penolakan.
import axios from 'axios'
axios . defaults . withCredentials = true ;
Kami telah memperkenalkan proses transfer konfigurasi di proyek axios di bagian cara kerja konfigurasi yang dikonfigurasi pengguna. Dari sini, kami dapat menyimpulkan bahwa konfigurasi yang kami buat melalui axios.defaults.withCredentials = true
ada di /lib/adapters/xhr.js
, dan kemudian dikonfigurasikan ke item objek xhr melalui kode berikut.
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 ) {
// 超时处理
}
} )
Sesuaikan rentang keberhasilan dan kegagalan kode status http
import axios from 'axios'
axios . defaults . validateStatus = status => status >= 200 && status < 300 ;
Dalam konfigurasi default, aturan verifikasi kode status http default ditentukan, sehingga penyesuaian validateStatus
sebenarnya merupakan penggantian metode di sini.
// `/lib/defaults.js`
var defaults = {
// ...
validateStatus : function validateStatus ( status ) {
return status >= 200 && status < 300 ;
} ,
// ...
}
Kapan axios mulai memvalidasi kode status http?
// /lib/adapters/xhr.js
var request = new XMLHttpRequest ( ) ;
var loadEvent = 'onreadystatechange' ;
// /lib/adapters/xhr.js
// 每当 readyState 改变时,就会触发 onreadystatechange 事件
request [ loadEvent ] = function handleLoad ( ) {
if ( ! request || ( request . readyState !== 4 && ! xDomain ) ) {
return ;
}
// ...省略代码
var response = {
// ...
// IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
status : request . status === 1223 ? 204 : request . status ,
config : config ,
} ;
settle ( resolve , reject , response ) ;
// ...省略代码
}
// /lib/core/settle.js
function settle ( resolve , reject , response ) {
// 如果我们往上捣一捣就会发现,config对象的validateStatus就是我们自定义的validateStatus方法或默认的validateStatus方法
var validateStatus = response . config . validateStatus ;
// validateStatus验证通过,就会触发resolve方法
if ( ! response . status || ! validateStatus || validateStatus ( response . status ) ) {
resolve ( response ) ;
} else {
reject ( createError (
'Request failed with status code ' + response . status ,
response . config ,
null ,
response . request ,
response
) ) ;
}
} ;
Dalam proyek axios, ada banyak cara cerdas untuk menggunakan JS, seperti operasi serial janji (tentu saja Anda juga dapat mengatakan bahwa ini didasarkan pada metode pemrosesan banyak middleware asynchronous), yang memungkinkan kita menangani berbagai macam dengan mudah proses sebelum dan sesudah permintaan. Mengontrol aliran setiap metode pemrosesan; banyak optimasi kecil yang praktis, seperti pemrosesan data sebelum dan sesudah permintaan, menyelamatkan pemrogram dari menulis JSON.xxx berulang kali yang mendukung lingkungan browser dan node, untuk proyek menggunakan node Tidak diragukan lagi luar biasa.
Singkatnya, bintang yang dapat memperoleh 42K+ di github (per 27.05.2018) ini sama sekali bukan perkiraan kekuatannya dan layak untuk diajak bicara!