axios攔截器的實現


攔截器設計與實現

#需求分析

我們希望能對請求的發送和響應做攔截,也就是在發送請求之前和接收到響應之后做一些額外邏輯。

我們希望設計的攔截器的使用方式如下:

// 添加一個請求攔截器 axios.interceptors.request.use(function (config) { // 在發送請求之前可以做一些事情 return config; }, function (error) { // 處理請求錯誤 return Promise.reject(error); }); // 添加一個響應攔截器 axios.interceptors.response.use(function (response) { // 處理響應數據 return response; }, function (error) { // 處理響應錯誤 return Promise.reject(error); }); 

在 axios 對象上有一個 interceptors 對象屬性,該屬性又有 request 和 response 2 個屬性,它們都有一個 use 方法,use 方法支持 2 個參數,第一個參數類似 Promise 的 resolve 函數,第二個參數類似 Promise 的 reject 函數。我們可以在 resolve 函數和 reject 函數中執行同步代碼或者是異步代碼邏輯。

並且我們是可以添加多個攔截器的,攔截器的執行順序是鏈式依次執行的方式。對於 request 攔截器,后添加的攔截器會在請求前的過程中先執行;對於 response 攔截器,先添加的攔截器會在響應后先執行。

axios.interceptors.request.use(config => { config.headers.test += '1' return config }) axios.interceptors.request.use(config => { config.headers.test += '2' return config }) 

此外,我們也可以支持刪除某個攔截器,如下:

const myInterceptor = axios.interceptors.request.use(function () {/*...*/}) axios.interceptors.request.eject(myInterceptor) 

#整體設計

我們先用一張圖來展示一下攔截器工作流程:

interceptor

整個過程是一個鏈式調用的方式,並且每個攔截器都可以支持同步和異步處理,我們自然而然地就聯想到使用 Promise 鏈的方式來實現整個調用過程。

在這個 Promise 鏈的執行過程中,請求攔截器 resolve 函數處理的是 config 對象,而相應攔截器 resolve 函數處理的是 response 對象。

在了解了攔截器工作流程后,我們先要創建一個攔截器管理類,允許我們去添加 刪除和遍歷攔截器。

#攔截器管理類實現

根據需求,axios 擁有一個 interceptors 對象屬性,該屬性又有 request 和 response 2 個屬性,它們對外提供一個 use 方法來添加攔截器,我們可以把這倆屬性看做是一個攔截器管理對象。use 方法支持 2 個參數,第一個是 resolve 函數,第二個是 reject 函數,對於 resolve 函數的參數,請求攔截器是 AxiosRequestConfig 類型的,而響應攔截器是 AxiosResponse 類型的;而對於 reject 函數的參數類型則是 any 類型的。

根據上述分析,我們先來定義一下攔截器管理對象對外的接口。

#接口定義

types/index.ts

export interface AxiosInterceptorManager<T> { use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number eject(id: number): void } export interface ResolvedFn<T=any> { (val: T): T | Promise<T> } export interface RejectedFn { (error: any): any } 

這里我們定義了 AxiosInterceptorManager 泛型接口,因為對於 resolve 函數的參數,請求攔截器和響應攔截器是不同的。

#代碼實現

import { ResolvedFn, RejectedFn } from '../types' interface Interceptor<T> { resolved: ResolvedFn<T> rejected?: RejectedFn } export default class InterceptorManager<T> { private interceptors: Array<Interceptor<T> | null> constructor() { this.interceptors = [] } use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number { this.interceptors.push({ resolved, rejected }) return this.interceptors.length - 1 } forEach(fn: (interceptor: Interceptor<T>) => void): void { this.interceptors.forEach(interceptor => { if (interceptor !== null) { fn(interceptor) } }) } eject(id: number): void { if (this.interceptors[id]) { this.interceptors[id] = null } } } 

我們定義了一個 InterceptorManager 泛型類,內部維護了一個私有屬性 interceptors,它是一個數組,用來存儲攔截器。該類還對外提供了 3 個方法,其中 use 接口就是添加攔截器到 interceptors中,並返回一個 id 用於刪除;forEach 接口就是遍歷 interceptors 用的,它支持傳入一個函數,遍歷過程中會調用該函數,並把每一個 interceptor 作為該函數的參數傳入;eject 就是刪除一個攔截器,通過傳入攔截器的 id 刪除。

#鏈式調用實現

本小節需要你對 Promise 掌握和理解,可以前往 mdn 學習。

當我們實現好攔截器管理類,接下來就是在 Axios 中定義一個 interceptors 屬性,它的類型如下:

interface Interceptors { request: InterceptorManager<AxiosRequestConfig> response: InterceptorManager<AxiosResponse> } export default class Axios { interceptors: Interceptors constructor() { this.interceptors = { request: new InterceptorManager<AxiosRequestConfig>(), response: new InterceptorManager<AxiosResponse>() } } } 

Interceptors 類型擁有 2 個屬性,一個請求攔截器管理類實例,一個是響應攔截器管理類實例。我們在實例化 Axios 類的時候,在它的構造器去初始化這個 interceptors 實例屬性。

接下來,我們修改 request 方法的邏輯,添加攔截器鏈式調用的邏輯:

core/Axios.ts

interface PromiseChain { resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise) rejected?: RejectedFn } request(url: any, config?: any): AxiosPromise { if (typeof url === 'string') { if (!config) { config = {} } config.url = url } else { config = url } const chain: PromiseChain[] = [{ resolved: dispatchRequest, rejected: undefined }] this.interceptors.request.forEach(interceptor => { chain.unshift(interceptor) }) this.interceptors.response.forEach(interceptor => { chain.push(interceptor) }) let promise = Promise.resolve(config) while (chain.length) { const { resolved, rejected } = chain.shift()! promise = promise.then(resolved, rejected) } return promise } 

首先,構造一個 PromiseChain 類型的數組 chain,並把 dispatchRequest 函數賦值給 resolved 屬性;接着先遍歷請求攔截器插入到 chain 的前面;然后再遍歷響應攔截器插入到 chain 后面。

接下來定義一個已經 resolve 的 promise,循環這個 chain,拿到每個攔截器對象,把它們的 resolved 函數和 rejected 函數添加到 promise.then 的參數中,這樣就相當於通過 Promise 的鏈式調用方式,實現了攔截器一層層的鏈式調用的效果。

注意我們攔截器的執行順序,對於請求攔截器,先執行后添加的,再執行先添加的;而對於響應攔截器,先執行先添加的,后執行后添加的。

#demo 編寫

在 examples 目錄下創建 interceptor 目錄,在 interceptor 目錄下創建 index.html:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Interceptor example</title> </head> <body> <script src="/__build__/interceptor.js"></script> </body> </html> 

接着創建 app.ts 作為入口文件:

import axios from '../../src/index' axios.interceptors.request.use(config => { config.headers.test += '1' return config }) axios.interceptors.request.use(config => { config.headers.test += '2' return config }) axios.interceptors.request.use(config => { config.headers.test += '3' return config }) axios.interceptors.response.use(res => { res.data += '1' return res }) let interceptor = axios.interceptors.response.use(res => { res.data += '2' return res }) axios.interceptors.response.use(res => { res.data += '3' return res }) axios.interceptors.response.eject(interceptor) axios({ url: '/interceptor/get', method: 'get', headers: { test: '' } }).then((res) => { console.log(res.data) }) 

該 demo 我們添加了 3 個請求攔截器,添加了 3 個響應攔截器並刪除了第二個。運行該 demo 我們通過瀏覽器訪問,我們發送的請求添加了一個 test 的請求 header,它的值是 321;我們的響應數據返回的是 hello,經過響應攔截器的處理,最終我們輸出的數據是 hello13

至此,我們給 ts-axios 實現了攔截器功能,它是一個非常實用的功能,在實際工作中我們可以利用它做一些需求如登錄權限認證。

我們目前通過 axios 發送請求,往往會傳入一堆配置,但是我們也希望 ts-axios 本身也會有一些默認配置,我們把用戶傳入的自定義配置和默認配置做一層合並。其實,大部分的 JS 庫都是類似的玩法。下面一章我們就來實現這個 feature。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM