axios는 브라우저와 node.js에서 사용할 수 있는 Promise 기반의 http 요청 라이브러리입니다. 현재 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 요청을 가로채고 반환된 결과 항목을 수정할 수 있습니다.
먼저 간략한 설명을 하고, 나중에 요청 응답을 가로채고 요청 매개변수와 응답 데이터를 수정하는 방법에 대해 자세히 소개하겠습니다.
데이터 변환기(객체를 JSON 문자열로 변환하는 등 실제로 데이터를 변환함)
데이터 변환기는 요청 변환기와 응답 변환기로 구분됩니다. 요청 변환기( transformRequest
)는 요청 전 데이터 변환을 의미하고 응답 변환기( transformResponse
)는 주로 요청 응답 후 응답 본문에 대한 데이터 변환을 수행합니다.
http 요청 어댑터(실제로는 메소드)
Axios 프로젝트에서 http 요청 어댑터는 주로 XHR과 http의 두 가지 유형을 나타냅니다. XHR의 핵심은 브라우저 측의 XMLHttpRequest 객체이고, http의 핵심은 노드의 http[s].request 메소드입니다.
물론 axios에서는 config를 통해 어댑터 인터페이스를 구성하는 일도 사용자에게 맡깁니다. 그러나 일반적인 상황에서는 이 두 어댑터가 브라우저에서 서버로의 요청 또는 노드의 http 클라이언트에서 서버로의 요청 요구 사항을 충족할 수 있습니다.
이 공유는 주로 XHR에 중점을 둡니다.
config 구성 항목(실제로는 객체)
여기서 말하는 config는 실제로 프로젝트에서 변수명 config라고 부르지는 않습니다. 이 이름은 여러분의 이해를 돕기 위해 제가 만든 이름입니다.
Axios 프로젝트에서 구성을 설정읽을 때 어떤 곳에서는 이를 defaults
( /lib/defaults.js
)이라고 부르고 여기에 기본 구성 항목이 있으며 어떤 곳에서는 Axios.prototype.request
의 매개변수와 같이 config
라고 부릅니다. 또 다른 예는 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
함수를 얻으려고 합니다. 이 함수는 Axios.prototype.request
의 모든 메서드를 정적 메서드로 가지며 이러한 메서드 Axios.prototype
컨텍스트는 다음과 같습니다. 같은 대상을 가리킨다.
그럼 Axios、Axios.prototype.request
의 소스코드를 살펴볼까요?
Axios 패키지의 핵심 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
및 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
두 가지 속성이 있습니다.
function Axios ( instanceConfig ) {
// ...
this . interceptors = {
request : new InterceptorManager ( ) ,
response : new InterceptorManager ( )
} ;
}
두 속성 모두 InterceptorManager
인스턴스이며 이 InterceptorManager
생성자는 인터셉터를 관리하는 데 사용됩니다.
먼저 InterceptorManager
생성자를 살펴보겠습니다.
InterceptorManager
생성자는 인터셉터를 구현하는 데 사용됩니다. 이 생성자의 프로토타입에는 use,ject 및 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 ) ;
}
} ) ;
} ;
그렇다면 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는 주로 세 가지 작업을 수행합니다. 1. 구성 개체를 가져와서 http 요청 어댑터에 전달하기 전에 구성에 대한 최종 처리를 수행합니다. 2. 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
메소드는 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 객체를 가져온 다음, xhrAdapter
에서 반환된 Promise 객체의 성공 또는 실패 결과를 .then
메소드를 통해 처리하고, 성공하면 처리된 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의 두 가지 채널을 이미 열었나요? 다음으로 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
의 인스턴스 속성을 가져오는 것입니다. 이 때 /lib/adapters/xhr.js
에서 promise
의 상태는 pending
. /lib/adapters/xhr.js
파일의 이 promise
인스턴스에 .then
메소드를 계속 추가합니다( xhr.js
파일 config.cancelToken.promise.then(message => request.abort())
의 159번째 줄) 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 메소드의 매개변수를 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
에서 얻을 수 있으며, 다음 코드를 통해 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 프로젝트에는 Promise의 직렬 작업(물론 이는 많은 비동기 미들웨어의 처리 방법을 기반으로 한다고 말할 수도 있음)과 같은 JS를 사용하는 영리한 방법이 많이 있습니다. 요청 전후의 다양한 프로세스를 통해 각 처리 방법의 흐름을 제어합니다. 예를 들어 요청 전후의 데이터 처리는 프로그래머가 JSON.xxx를 반복해서 작성하는 것을 방지하며 브라우저와 노드 환경을 모두 지원합니다. 노드를 사용하는 프로젝트에 적합합니다. 의심의 여지없이 우수합니다.
요컨대, github(2018.05.27 기준)에서 42K+를 얻을 수 있는 이 스타의 힘은 결코 불가능하지 않으며, 이야기할 가치가 있습니다!