axios เป็นไลบรารีคำขอ http ที่ใช้ Promise ซึ่งสามารถใช้ในเบราว์เซอร์และ node.js ปัจจุบันมีดาว 42,000 ดวงบน 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 # 入口文件
หมายเหตุ: เนื่องจากโค้ดที่เราต้องดูคือไฟล์ทั้งหมดในไดเร็กทอรี /lib/
เราจะค้นหาพาธของไฟล์ต่อไปนี้ทั้งหมดภายใต้ /lib/
เครื่องดักฟัง
(ถ้าคุณคุ้นเคยกับมิดเดิลแวร์ก็จะเข้าใจได้ง่ายเพราะมีบทบาทเป็นมิดเดิลแวร์ตามสัญญา)
Interceptors แบ่งออกเป็น request interceptors และ response interceptors ตามชื่อ: request interceptors ( interceptors.request
) สามารถสกัดกั้นคำขอ http แต่ละรายการหรือที่ระบุ และสามารถแก้ไขรายการการกำหนดค่า Response interceptors ( interceptors.response
) สามารถใช้ได้ทุกครั้ง หลังจากการร้องขอ http หลายครั้ง ให้สกัดกั้นคำขอ http แต่ละรายการหรือที่ระบุ และรายการผลลัพธ์ที่ส่งคืนสามารถแก้ไขได้
ต่อไปนี้เป็นคำอธิบายสั้นๆ ก่อน จากนั้นเราจะให้คำแนะนำโดยละเอียดเกี่ยวกับวิธีการสกัดกั้นการตอบสนองคำขอและแก้ไขพารามิเตอร์คำขอและข้อมูลการตอบสนอง
ตัวแปลงข้อมูล (จริงๆ แล้วแปลงข้อมูล เช่น การแปลงออบเจ็กต์เป็นสตริง JSON)
ตัวแปลงข้อมูลแบ่งออกเป็นตัวแปลงคำขอและตัวแปลงการตอบสนอง ตามความหมายของชื่อ: ตัวแปลงคำขอ ( transformRequest
) หมายถึงการแปลงข้อมูลก่อนคำขอ และตัวแปลงการตอบสนอง ( transformResponse
) จะทำการแปลงข้อมูลบนเนื้อหาการตอบสนองเป็นหลักหลังจากการตอบกลับคำขอ
อะแดปเตอร์คำขอ http (จริงๆ แล้วเป็นวิธีการ)
ในโปรเจ็กต์ axios อะแด็ปเตอร์คำขอ http อ้างอิงถึงสองประเภทเป็นหลัก: XHR และ http แกนหลักของ XHR คือออบเจ็กต์ XMLHttpRequest บนฝั่งเบราว์เซอร์ และแกนหลักของ http คือเมธอด http[s].request ของโหนด
แน่นอนว่า axios ยังปล่อยให้ผู้ใช้กำหนดค่าอินเทอร์เฟซของอะแดปเตอร์ผ่านการกำหนดค่า อย่างไรก็ตาม ภายใต้สถานการณ์ปกติ อะแดปเตอร์ทั้งสองนี้สามารถตอบสนองคำขอจากเบราว์เซอร์ไปยังเซิร์ฟเวอร์ หรือจากไคลเอ็นต์ http ของโหนดไปยังเซิร์ฟเวอร์ที่ร้องขอได้
การแบ่งปันนี้มุ่งเน้นไปที่ XHR เป็นหลัก
รายการการกำหนดค่า config (จริงๆ แล้วเป็นวัตถุ)
การกำหนดค่าที่เรากำลังพูดถึงในที่นี้ไม่ได้เรียกว่าการกำหนดค่าชื่อตัวแปรในโปรเจ็กต์จริงๆ ชื่อนี้เป็นชื่อที่ฉันให้ตามจุดประสงค์เพื่ออำนวยความสะดวกในการทำความเข้าใจของทุกคน
ในโปรเจ็กต์ axios เมื่อตั้งค่าอ่านการกำหนดค่า บางแห่งเรียกมันว่า 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 ,
} )
วิธีที่สองในการใช้: axios(url[, option])
axios ( url , {
method ,
headers ,
} )
วิธีการใช้งานที่สาม (สำหรับ get、delete
ฯลฯ): axios[method](url[, option])
axios . get ( url , {
headers ,
} )
วิธีการใช้งานที่สี่ (สำหรับวิธี post、put
ฯลฯ): axios[method](url[, data[, option]])
axios . post ( url , data , {
headers ,
} )
วิธีที่ห้าในการใช้: 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
หวังว่าจะได้รับ Function Axios.prototype
ในที่สุด Function นี้จะชี้ไปที่ Axios.prototype.request
ชี้ไปที่วัตถุเดียวกัน
มาดูซอร์สโค้ดของ 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
} ) ) ;
} ;
} ) ;
ด้วยโค้ดข้างต้น เราสามารถเริ่มต้นคำขอ http ได้หลายวิธี: axios()、axios.get()、axios.post()
โดยทั่วไป โปรเจ็กต์สามารถตอบสนองความต้องการได้โดยใช้อินสแตนซ์ 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
และ config
ค่าพารามิเตอร์ request
ถูกรวมเข้าด้วยกันที่นี่
จากนี้ ลำดับความสำคัญของการกำหนดค่าหลายรายการจากต่ำไปสูงคือ: —> defaults
เริ่มต้นของวัตถุการกำหนดค่าเริ่มต้น ( /lib/defaults.js
)
-> { วิธีการ: 'รับ' }
-> คุณสมบัติอินสแตนซ์ Axios this.defaults
-> request
config
ค่าพารามิเตอร์
ฉันฝากคำถามไว้ให้คุณ: เมื่อใดที่การกำหนด defaults
และ this.defaults
จะเหมือนกัน และเมื่อใดจะแตกต่างกัน
จนถึงตอนนี้ เราได้รับอ็อบเจ็กต์ config
หลังจาก merge
หลาย ๆ ที่ แล้วอ็อบเจ็กต์นี้จะถูกถ่ายโอนในโปรเจ็กต์อย่างไร
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
โค้ดที่นี่ค่อนข้างซับซ้อน และวิธีการบางอย่างจำเป็นต้องตรวจสอบกลับไปยังแหล่งที่มาเพื่อหาคำตอบ ดังนั้นคุณเพียงแค่ต้องมีความเข้าใจอย่างง่ายเกี่ยวกับ chain array เท่านั้น ส่วน interceptors และ [ dispatchRequest
] ที่เกี่ยวข้องจะถูกนำมาใช้ในรายละเอียดในภายหลัง .
อาร์เรย์ chain
ใช้เพื่อเก็บวิธี interceptor และวิธี dispatchRequest
ฟังก์ชันการโทรกลับจะถูกนำออกจากอาร์เรย์ chain
ตามลำดับผ่านสัญญาและดำเนินการทีละรายการ สุดท้ายสัญญาใหม่ที่ประมวลผลจะถูกส่งกลับในวิธี 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 ;
} ;
ณ จุดนี้ คุณจะต้องมีความอยากรู้อยากเห็นเกี่ยวกับ Interceptor อย่างแน่นอน ให้เราค้นหาคำตอบในหัวข้อถัดไป
// 添加请求拦截器
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
และมี request
และ response
คุณลักษณะสองรายการบนวัตถุ interceptors
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
คุณสมบัติทั้งสองเป็นอินสแตนซ์ InterceptorManager
และตัวสร้าง InterceptorManager
นี้ใช้เพื่อจัดการตัวดัก
ก่อนอื่นเรามาดูที่ตัวสร้าง InterceptorManager
:
ตัวสร้าง InterceptorManager
ถูกใช้เพื่อนำตัวดักจับไปใช้ มีสามวิธีในต้นแบบของตัวสร้างนี้: ใช้, ดีดออก และ forEach เกี่ยวกับซอร์สโค้ด จริงๆ แล้วค่อนข้างง่ายและใช้เพื่อดำเนินการแอ็ตทริบิวต์อินสแตนซ์ตัวจัดการของตัวสร้าง
// /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 ) ;
}
} ) ;
} ;
ดังนั้นเมื่อเราเพิ่ม interceptors ผ่าน axios.interceptors.request.use
แล้ว axios จะทำให้ interceptor เหล่านี้ภายในสามารถรับข้อมูลที่เราต้องการก่อนและหลังคำขอได้อย่างไร
มาดูโค้ดกันก่อน:
// /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 ;
} ;
ตอนนี้คุณควรมีความคิดที่ชัดเจนว่า Interceptor คืออะไรและทำงานอย่างไรในเมธอด Axios.prototype.request
แล้ว dispatchRequest
ใน "ตำแหน่งกลางน้ำ" ส่งคำขอ http อย่างไร
DispatchRequest จะทำสามสิ่งเป็นหลัก: 1. รับออบเจ็กต์การกำหนดค่าและดำเนินการประมวลผลขั้นสุดท้ายกับการกำหนดค่าก่อนที่จะส่งต่อไปยังอะแดปเตอร์คำขอ http 2. อะแดปเตอร์คำขอ http เริ่มต้นคำขอตามการกำหนดค่าการกำหนดค่า 3. หลังจากอะแดปเตอร์คำขอ http คำขอเสร็จสมบูรณ์หากสำเร็จ ก็จะได้รับการตอบสนองหลังจากการแปลงข้อมูลตามส่วนหัว ข้อมูล และ config.transformResponse (เกี่ยวกับ TransformerResponse ตัวแปลงข้อมูลด้านล่างนี้จะอธิบาย) แล้วส่งคืน
// /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 ใช้คำสัญญาในการสร้างสะพานแบบอะซิงโครนัสโดยใช้ XHR อย่างไร
axios ทำการประมวลผลแบบอะซิงโครนัสผ่าน Promise อย่างไร
import axios from 'axios'
axios . get ( /**/ )
. then ( data => {
// 此处可以拿到向服务端请求回的数据
} )
. catch ( error => {
// 此处可以拿到请求失败或取消或其他处理失败的错误对象
} )
ก่อนอื่น เรามาทำความเข้าใจคร่าวๆ กันก่อนถึงลำดับโฟลว์ของการเข้าถึงผู้ใช้ หลังจากที่คำขอ http เสร็จสิ้นในโปรเจ็กต์ axios:
เรารู้ว่าเหตุใด 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
ส่งคืนอ็อบเจ็กต์ Promise
// /lib/adapters/xhr.js
function xhrAdapter ( config ) {
return new Promise ( function dispatchXhrRequest ( resolve , reject ) {
// ... 省略代码
} ) ;
} ;
หลังจากที่ XHR ใน xhrAdapter
ส่งคำขอสำเร็จแล้ว มันจะดำเนินการวิธี resolve
ของออบเจ็กต์ Promise และส่งข้อมูลที่ร้องขอออกไป มิฉะนั้นจะดำเนินการวิธี 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
ขั้นแรกรับอ็อบเจ็กต์ Promise ที่ส่งคืนโดยวิธี xhrAdapter
จากนั้นประมวลผลผลลัพธ์สำเร็จหรือล้มเหลวของอ็อบเจ็กต์ Promise ที่ส่งคืนโดย xhrAdapter
ผ่าน .then
.then หากสำเร็จ response
ที่ประมวลผลจะถูกส่งคืน ล้มเหลว จากนั้นส่งคืนออบเจ็กต์ Promise ที่มีสถานะ rejected
return adapter ( config ) . then ( function onAdapterResolution ( response ) {
// ...
return response ;
} , function onAdapterRejection ( reason ) {
// ...
return Promise . reject ( reason ) ;
} ) ;
} ;
ดังนั้น ณ จุดนี้ เมื่อผู้ใช้เรียกใช้เมธอด axios()
เขาจะสามารถเรียก .then
หรือ .catch
ของ Promise ได้โดยตรงสำหรับการประมวลผลทางธุรกิจ
เมื่อมองย้อนกลับไป เราได้พูดคุยเกี่ยวกับการแปลงข้อมูลเมื่อแนะนำ 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 ) ;
} ) ;
Interceptor ยังสามารถตระหนักถึงความจำเป็นในการแปลงข้อมูลคำขอและการตอบสนอง แต่ตามการออกแบบของผู้เขียนและรหัสที่ครอบคลุม จะเห็นได้ว่าในระหว่างการร้องขอ Interceptor มีหน้าที่หลักในการแก้ไขรายการการกำหนดค่า config และตัวแปลงข้อมูลคือ รับผิดชอบในการแปลงเนื้อหาคำขอเป็นหลัก เช่น การแปลงวัตถุ หลังจากร้องขอการตอบสนองสำหรับสตริงแล้ว ผู้สกัดกั้นสามารถรับ 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 แล้วหรือยัง ต่อไปเรามาดูกันว่า 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 ;
} ) ;
}
แกนหลักของฟังก์ชันการยกเลิกคือการได้รับแอตทริบิวต์อินสแตนซ์ที่ promise
ผ่าน this.promise = new Promise(resolve => resolvePromise = resolve)
ใน CancelToken ในขณะนี้ สถานะของ promise
อยู่ pending
ผ่านแอตทริบิวต์นี้ใน /lib/adapters/xhr.js
เพิ่มเมธอด .then
then ให้กับอินสแตนซ์ promise
นี้ในไฟล์ต่อไป (บรรทัดที่ 159 ของไฟล์ xhr.js
config.cancelToken.promise.then(message => request.abort())
);
ภายนอก CancelToken
พารามิเตอร์ executor
จะถูกใช้เพื่อควบคุมวิธี cancel
ด้วยวิธีนี้ เมื่อดำเนินการวิธี cancel
สถานะของแอตทริบิวต์ promise
ของอินสแตนซ์สามารถเปลี่ยนเป็น rejected
ดังนั้นจึงดำเนินการ request.abort()
วิธีการยกเลิกการร้องขอ
วิธีเขียนที่สองข้างต้นถือเป็นการปรับปรุงวิธีเขียนแบบแรก เนื่องจากหลายครั้งวิธีการยกเลิกคำขอของเราถูกใช้นอกวิธีคำขอปัจจุบัน ตัวอย่างเช่น ถ้าเราส่งคำขอ 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请求成功了' ) ) ;
ในทางกลับกัน ฉันชอบวิธีเขียนแบบแรกมากกว่า เพราะวิธีเขียนแบบที่สองซ่อนเร้นเกินไป และไม่สัญชาตญาณและเข้าใจง่ายเหมือนวิธีแรก
ในไฟล์ /lib/adapters/xhr.js ไม่ควรเรียกพารามิเตอร์ของเมธอด onCanceled เพราะเหตุใดจึงเรียกว่ายกเลิก
ในไฟล์ /lib/adapters/xhr.js ในเมธอด onCanceled ข้อมูลการกำหนดค่าควรถูกส่งออกไปเป็นการปฏิเสธด้วย
import axios from 'axios'
axios . defaults . withCredentials = true ;
เราได้แนะนำกระบวนการถ่ายโอนการกำหนดค่าในโปรเจ็กต์ axios ในส่วนเกี่ยวกับวิธีการทำงานของการกำหนดค่าที่ผู้ใช้กำหนด จากนี้ เราสามารถสรุปได้ว่าการกำหนดค่าที่เราทำผ่าน axios.defaults.withCredentials = true
อยู่ใน /lib/adapters/xhr.js
จากนั้นกำหนดค่าให้กับรายการอ็อบเจ็กต์ xhr โดยใช้โค้ดต่อไปนี้
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 มีวิธีที่ชาญฉลาดมากมายในการใช้ JS เช่น การดำเนินการแบบอนุกรมของสัญญา (แน่นอน คุณยังสามารถพูดได้ว่านี่ขึ้นอยู่กับวิธีการประมวลผลของมิดเดิลแวร์แบบอะซิงโครนัสจำนวนมาก) ซึ่งช่วยให้เราจัดการต่างๆ ได้อย่างง่ายดาย ประมวลผลก่อนและหลังคำขอ ควบคุมโฟลว์ของวิธีการประมวลผลแต่ละวิธี เช่น การประมวลผลข้อมูลก่อนและหลังคำขอ ช่วยให้โปรแกรมเมอร์ไม่ต้องเขียน JSON.xxx ซ้ำแล้วซ้ำเล่าสำหรับโปรเจ็กต์ การใช้โหนดนั้นยอดเยี่ยมอย่างไม่ต้องสงสัย
กล่าวโดยสรุป ดาวดวงนี้ที่สามารถได้รับ 42K+ บน GitHub (ณ วันที่ 05.27.2018) ไม่ได้เป็นการประเมินความแข็งแกร่งของเขาและคุ้มค่าที่จะพูดคุยด้วย!