Axios 如何實(shí)現(xiàn)請(qǐng)求重試?
在 Axios 如何取消重復(fù)請(qǐng)求? 這篇文章中,阿寶哥介紹了在 Axios 中如何取消重復(fù)請(qǐng)求及 CancelToken 的工作原理。而本文將介紹在 Axios 中如何通過 攔截器或適配器 來實(shí)現(xiàn)請(qǐng)求重試的功能。那么為什么要進(jìn)行請(qǐng)求重試呢?這是因?yàn)樵谀承┣闆r下,比如請(qǐng)求超時(shí)的時(shí)候,我們希望能自動(dòng)重新發(fā)起請(qǐng)求進(jìn)行重試操作,從而完成對(duì)應(yīng)的操作。
下面阿寶哥將介紹如何使用 Axios 提供的攔截器或適配器來實(shí)現(xiàn)請(qǐng)求重試的功能,如果你對(duì) Axios 的攔截器或適配器還不熟悉的話,建議先閱讀 77.9K 的 Axios 項(xiàng)目有哪些值得借鑒的地方 這篇文章。接下來,我們先來介紹如何使用攔截器實(shí)現(xiàn)請(qǐng)求重試的方案。
一、攔截器實(shí)現(xiàn)請(qǐng)求重試的方案
Axios 是一個(gè)基于 Promise 的 HTTP 客戶端,而 HTTP 協(xié)議是基于請(qǐng)求和響應(yīng):
所以 Axios 提供了 請(qǐng)求攔截器和響應(yīng)攔截器 來分別處理請(qǐng)求和響應(yīng),它們的作用如下:
- 請(qǐng)求攔截器:該類攔截器的作用是在請(qǐng)求發(fā)送前統(tǒng)一執(zhí)行某些操作,比如在請(qǐng)求頭中添加 token 字段。
 - 響應(yīng)攔截器:該類攔截器的作用是在接收到服務(wù)器響應(yīng)后統(tǒng)一執(zhí)行某些操作,比如發(fā)現(xiàn)響應(yīng)狀態(tài)碼為 401 時(shí),自動(dòng)跳轉(zhuǎn)到登錄頁。
 
在 Axios 中設(shè)置攔截器很簡單,通過 axios.interceptors.request 和 axios.interceptors.response 對(duì)象提供的 use 方法,就可以分別設(shè)置請(qǐng)求攔截器和響應(yīng)攔截器:
- export interface AxiosInstance {
 - interceptors: {
 - request: AxiosInterceptorManager<AxiosRequestConfig>;
 - response: AxiosInterceptorManager<AxiosResponse>;
 - };
 - }
 - export interface AxiosInterceptorManager<V> {
 - use(onFulfilled?: (value: V) => V | Promise<V>,
 - onRejected?: (error: any) => any): number;
 - eject(id: number): void;
 - }
 
對(duì)于請(qǐng)求重試的功能來說,我們希望讓用戶不僅能夠設(shè)置重試次數(shù),而且可以設(shè)置重試延時(shí)時(shí)間。當(dāng)請(qǐng)求失敗的時(shí)候,若該請(qǐng)求的配置對(duì)象配置了重試次數(shù),而 Axios 就會(huì)重新發(fā)起請(qǐng)求進(jìn)行重試操作。為了能夠全局進(jìn)行請(qǐng)求重試,接下來我們?cè)陧憫?yīng)攔截器上來實(shí)現(xiàn)請(qǐng)求重試功能,具體代碼如下所示:
- axios.interceptors.response.use(null, (err) => {
 - let config = err.config;
 - if (!config || !config.retryTimes) return Promise.reject(err);
 - const { __retryCount = 0, retryDelay = 300, retryTimes } = config;
 - // 在請(qǐng)求對(duì)象上設(shè)置重試次數(shù)
 - config.__retryCount = __retryCount;
 - // 判斷是否超過了重試次數(shù)
 - if (__retryCount >= retryTimes) {
 - return Promise.reject(err);
 - }
 - // 增加重試次數(shù)
 - config.__retryCount++;
 - // 延時(shí)處理
 - const delay = new Promise((resolve) => {
 - setTimeout(() => {
 - resolve();
 - }, retryDelay);
 - });
 - // 重新發(fā)起請(qǐng)求
 - return delay.then(function () {
 - return axios(config);
 - });
 - });
 
以上的代碼并不會(huì)復(fù)雜,對(duì)應(yīng)的處理流程如下圖所示:
介紹完如何使用攔截器實(shí)現(xiàn)請(qǐng)求重試的功能之后,下面阿寶哥來介紹適配器實(shí)現(xiàn)請(qǐng)求重試的方案。
二、適配器實(shí)現(xiàn)請(qǐng)求重試的方案
Axios 引入了適配器,使得它可以同時(shí)支持瀏覽器和 Node.js 環(huán)境。對(duì)于瀏覽器環(huán)境來說,它通過封裝 XMLHttpRequest API 來發(fā)送 HTTP 請(qǐng)求,而對(duì)于 Node.js 環(huán)境來說,它通過封裝 Node.js 內(nèi)置的 http 和 https 模塊來發(fā)送 HTTP 請(qǐng)求。
在 Axios 如何緩存請(qǐng)求數(shù)據(jù)? 這篇文章中,阿寶哥介紹了如何通過增強(qiáng)默認(rèn)的 Axios 適配器,來實(shí)現(xiàn)緩存請(qǐng)求數(shù)據(jù)的功能。同樣,采用類似的思路,我們也可以通過增強(qiáng)默認(rèn)的 Axios 適配器來實(shí)現(xiàn)請(qǐng)求重試的功能。
在介紹如何增強(qiáng)默認(rèn)適配器之前,我們先來看一下 Axios 內(nèi)置的 xhrAdapter 適配器,它被定義在 lib/adapters/xhr.js 文件中:
- // lib/adapters/xhr.js
 - module.exports = function xhrAdapter(config) {
 - return new Promise(function dispatchXhrRequest(resolve, reject) {
 - var requestData = config.data;
 - var requestHeaders = config.headers;
 - var request = new XMLHttpRequest();
 - // 省略大部分代碼
 - var fullPath = buildFullPath(config.baseURL, config.url);
 - request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
 - // Set the request timeout in MS
 - request.timeout = config.timeout;
 - // Listen for ready state
 - request.onreadystatechange = function handleLoad() { ... }
 - // Send the request
 - request.send(requestData);
 - });
 - };
 
很明顯 xhrAdapter 適配器是一個(gè)函數(shù)對(duì)象,它接收一個(gè) config 參數(shù)并返回一個(gè) Promise 對(duì)象。而在 xhrAdapter 適配器內(nèi)部,最終會(huì)使用 XMLHttpRequest API 來發(fā)送 HTTP 請(qǐng)求。為了實(shí)現(xiàn)請(qǐng)求重試的功能,我們就可以考慮通過高階函數(shù)來增強(qiáng) xhrAdapter適配器的功能。
2.1 定義 retryAdapterEnhancer 函數(shù)
為了讓用戶能夠更靈活地控制請(qǐng)求重試的功能,我們定義了一個(gè) retryAdapterEnhancer函數(shù),該函數(shù)支持兩個(gè)參數(shù):
- adapter:預(yù)增強(qiáng)的 Axios 適配器對(duì)象;
 - options:緩存配置對(duì)象,該對(duì)象支持 2 個(gè)屬性,分別用于配置不同的功能:
 - times:全局設(shè)置請(qǐng)求重試的次數(shù);
 - delay:全局設(shè)置請(qǐng)求延遲的時(shí)間,單位是 ms。
 
了解完 retryAdapterEnhancer 函數(shù)的參數(shù)之后,我們來看一下該函數(shù)的具體實(shí)現(xiàn):
- function retryAdapterEnhancer(adapter, options) {
 - const { times = 0, delay = 300 } = options;
 - return async (config) => {
 - const { retryTimes = times, retryDelay = delay } = config;
 - let __retryCount = 0;
 - const request = async () => {
 - try {
 - return await adapter(config);
 - } catch (err) {
 - // 判斷是否進(jìn)行重試
 - if (!retryTimes || __retryCount >= retryTimes) {
 - return Promise.reject(err);
 - }
 - __retryCount++; // 增加重試次數(shù)
 - // 延時(shí)處理
 - const delay = new Promise((resolve) => {
 - setTimeout(() => {
 - resolve();
 - }, retryDelay);
 - });
 - // 重新發(fā)起請(qǐng)求
 - return delay.then(() => {
 - return request();
 - });
 - }
 - };
 - return request();
 - };
 - }
 
以上的代碼并不會(huì)復(fù)雜,核心的處理邏輯如下圖所示:
2.2 使用 retryAdapterEnhancer 函數(shù)
2.2.1 創(chuàng)建 Axios 對(duì)象并配置 adapter 選項(xiàng)
- const http = axios.create({
 - baseURL: "http://localhost:3000/",
 - adapter: retryAdapterEnhancer(axios.defaults.adapter, {
 - retryDelay: 1000,
 - }),
 - });
 
2.2.2 使用 http 對(duì)象發(fā)送請(qǐng)求
- // 請(qǐng)求失敗不重試
 - function requestWithoutRetry() {
 - http.get("/users");
 - }
 - // 請(qǐng)求失敗重試
 - function requestWithRetry() {
 - http.get("/users", { retryTimes: 2 });
 - }
 
好了,如何通過增強(qiáng) xhrAdapter 適配器來實(shí)現(xiàn) Axios 請(qǐng)求重試的功能已經(jīng)介紹完了。由于完整的示例代碼內(nèi)容比較多,阿寶哥就不放具體的代碼了。感興趣的小伙伴,可以訪問以下地址瀏覽示例代碼。
- 完整的示例代碼:
 - https://gist.github.com/semlinker/979ebc659abacea7aa6c0c44af070afe
 
這里我們來看一下 Axios 實(shí)現(xiàn)請(qǐng)求重試示例的運(yùn)行結(jié)果:
三、總結(jié)
本文介紹了在 Axios 中如何實(shí)現(xiàn)請(qǐng)求重試,基于文中定義的 retryAdapterEnhancer 函數(shù)或響應(yīng)攔截器,你可以輕松地?cái)U(kuò)展請(qǐng)求重試的功能。
Axios 是一個(gè)很優(yōu)秀的開源項(xiàng)目,里面有很多值得我們學(xué)習(xí)與借鑒的地方。如果你對(duì) Axios 內(nèi)部 HTTP 攔截器的設(shè)計(jì)與實(shí)現(xiàn)、HTTP 適配器的設(shè)計(jì)與實(shí)現(xiàn)及如何防御 CSRF 攻擊感興趣的話,可以閱讀 77.9K 的 Axios 項(xiàng)目有哪些值得借鑒的地方 這篇文章。
四、參考資源
- Github - axios-extensions
 - Axios 如何取消重復(fù)請(qǐng)求?
 - Axios 如何緩存請(qǐng)求數(shù)據(jù)?
 - 77.9K 的 Axios 項(xiàng)目有哪些值得借鑒的地方
 




















 
 
 















 
 
 
 