axios如何利用promise無痛刷新token

目錄

需求

需求解析

實現思路

方法一:

方法二:

兩種方法對比

實現

封裝axios基本骨架

instance.interceptors.response.use攔截實現

問題和優化

如何防止多次刷新token

同時發起兩個或以上的請求時,其他接口如何重試

最后完整代碼


?

需求

最近遇到個需求:前端登錄后,后端返回tokentoken有效時間,當token過期時要求用舊token去獲取新的token,前端需要做到無痛刷新token,即請求刷新token時要做到用戶無感知。

需求解析

當用戶發起一個請求時,判斷token是否已過期,若已過期則先調refreshToken接口,拿到新的token后再繼續執行之前的請求。

這個問題的難點在于:當同時發起多個請求,而刷新token的接口還沒返回,此時其他請求該如何處理?接下來會循序漸進地分享一下整個過程。

實現思路

由于后端返回了token的有效時間,可以有兩種方法:

方法一:

在請求發起前攔截每個請求,判斷token的有效時間是否已經過期,若已過期,則將請求掛起,先刷新token后再繼續請求。

方法二:

不在請求前攔截,而是攔截返回后的數據。先發起請求,接口返回過期后,先刷新token,再進行一次重試。

兩種方法對比

方法一

  • 優點: 在請求前攔截,能節省請求,省流量。
  • 缺點: 需要后端額外提供一個token過期時間的字段;使用了本地時間判斷,若本地時間被篡改,特別是本地時間比服務器時間慢時,攔截會失敗。
PS:token有效時間建議是時間段,類似緩存的MaxAge,而不要是絕對時間。當服務器和本地時間不一致時,絕對時間會有問題。

方法二

  • 優點:不需額外的token過期字段,不需判斷時間。
  • 缺點: 會消耗多一次請求,耗流量。

綜上,方法一和二優缺點是互補的,方法一有校驗失敗的風險(本地時間被篡改時,當然一般沒有用戶閑的蛋疼去改本地時間的啦),方法二更簡單粗暴,等知道服務器已經過期了再重試一次,只是會耗多一個請求。

在這里博主選擇了?方法二

實現

這里會使用axios來實現,方法一是請求前攔截,所以會使用axios.interceptors.request.use()這個方法;

而方法二是請求后攔截,所以會使用axios.interceptors.response.use()方法。

封裝axios基本骨架

首先說明一下,項目中的token是存在localStorage中的。request.js基本骨架:

import axios from 'axios'// 從localStorage中獲取token
function getLocalToken () {const token = window.localStorage.getItem('token')return token
}// 給實例添加一個setToken方法,用于登錄后將最新token動態添加到header,同時將token保存在localStorage中
instance.setToken = (token) => {instance.defaults.headers['X-Token'] = tokenwindow.localStorage.setItem('token', token)
}// 創建一個axios實例
const instance = axios.create({baseURL: '/api',timeout: 300000,headers: {'Content-Type': 'application/json','X-Token': getLocalToken() // headers塞token}
})// 攔截返回的數據
instance.interceptors.response.use(response => {// 接下來會在這里進行token過期的邏輯處理return response
}, error => {return Promise.reject(error)
})export default instance

這個是項目中一般的axios實例的封裝,創建實例時,將本地已有的token放進header,然后export出去供調用。接下來就是如何攔截返回的數據啦。

instance.interceptors.response.use攔截實現

后端接口一般會有一個約定好的數據結構,如:

{code: 1234, message: 'token過期', data: {}}

如我這里,后端約定當code === 1234時表示token過期了,此時就要求刷新token。

instance.interceptors.response.use(response => {const { code } = response.dataif (code === 1234) {// 說明token過期了,刷新tokenreturn refreshToken().then(res => {// 刷新token成功,將最新的token更新到header中,同時保存在localStorage中const { token } = res.datainstance.setToken(token)// 獲取當前失敗的請求const config = response.config// 重置一下配置config.headers['X-Token'] = tokenconfig.baseURL = '' // url已經帶上了/api,避免出現/api/api的情況// 重試當前請求并返回promisereturn instance(config)}).catch(res => {console.error('refreshtoken error =>', res)//刷新token失敗,神仙也救不了了,跳轉到首頁重新登錄吧window.location.href = '/'})}return response
}, error => {return Promise.reject(error)
})function refreshToken () {// instance是當前request.js中已創建的axios實例return instance.post('/refreshtoken').then(res => res.data)
}

這里需要額外注意的是,response.config就是原請求的配置,但這個是已經處理過了的,config.url已經帶上了baseUrl,因此重試時需要去掉,同時token也是舊的,需要刷新下。

以上就基本做到了無痛刷新token,當token正常時,正常返回,當token已過期,則axios內部進行一次刷新token和重試。對調用者來說,axios內部的刷新token是一個黑盒,是無感知的,因此需求已經做到了。

問題和優化

上面的代碼還是存在一些問題的,沒有考慮到多次請求的問題,因此需要進一步優化。

如何防止多次刷新token

如果refreshToken接口還沒返回,此時再有一個過期的請求進來,上面的代碼就會再一次執行refreshToken,這就會導致多次執行刷新token的接口,因此需要防止這個問題。我們可以在request.js中用一個flag來標記當前是否正在刷新token的狀態,如果正在刷新則不再調用刷新token的接口。

// 是否正在刷新的標記
let isRefreshing = false
instance.interceptors.response.use(response => {const { code } = response.dataif (code === 1234) {if (!isRefreshing) {isRefreshing = truereturn refreshToken().then(res => {const { token } = res.datainstance.setToken(token)const config = response.configconfig.headers['X-Token'] = tokenconfig.baseURL = ''return instance(config)}).catch(res => {console.error('refreshtoken error =>', res)window.location.href = '/'}).finally(() => {isRefreshing = false})}}return response
}, error => {return Promise.reject(error)
})

這樣子就可以避免在刷新token時再進入方法了。但是這種做法是相當于把其他失敗的接口給舍棄了,假如同時發起兩個請求,且幾乎同時返回,第一個請求肯定是進入了refreshToken后再重試,而第二個請求則被丟棄了,仍是返回失敗,所以接下來還得解決其他接口的重試問題。

同時發起兩個或以上的請求時,其他接口如何重試

兩個接口幾乎同時發起和返回,第一個接口會進入刷新token后重試的流程,而第二個接口需要先存起來,然后等刷新token后再重試。同樣,如果同時發起三個請求,此時需要緩存后兩個接口,等刷新token后再重試。由于接口都是異步的,處理起來會有點麻煩。

當第二個過期的請求進來,token正在刷新,我們先將這個請求存到一個數組隊列中,想辦法讓這個請求處于等待中,一直等到刷新token后再逐個重試清空請求隊列。
那么如何做到讓這個請求處于等待中呢?為了解決這個問題,我們得借助Promise。將請求存進隊列中后,同時返回一個Promise,讓這個Promise一直處于Pending狀態(即不調用resolve),此時這個請求就會一直等啊等,只要我們不執行resolve,這個請求就會一直在等待。當刷新請求的接口返回來后,我們再調用resolve,逐個重試。最終代碼:

// 是否正在刷新的標記
let isRefreshing = false
// 重試隊列,每一項將是一個待執行的函數形式
let requests = []instance.interceptors.response.use(response => {const { code } = response.dataif (code === 1234) {const config = response.configif (!isRefreshing) {isRefreshing = truereturn refreshToken().then(res => {const { token } = res.datainstance.setToken(token)config.headers['X-Token'] = tokenconfig.baseURL = ''// 已經刷新了token,將所有隊列中的請求進行重試requests.forEach(cb => cb(token))// 重試完了別忘了清空這個隊列(掘金評論區同學指點)requests = []return instance(config)}).catch(res => {console.error('refreshtoken error =>', res)window.location.href = '/'}).finally(() => {isRefreshing = false})} else {// 正在刷新token,返回一個未執行resolve的promisereturn new Promise((resolve) => {// 將resolve放進隊列,用一個函數形式來保存,等token刷新后直接執行requests.push((token) => {config.baseURL = ''config.headers['X-Token'] = tokenresolve(instance(config))})})}}return response
}, error => {return Promise.reject(error)
})

這里可能比較難理解的是requests這個隊列中保存的是一個函數,這是為了讓resolve不執行,先存起來,等刷新token后更方便調用這個函數使得resolve執行。至此,問題應該都解決了

最后完整代碼

import axios from 'axios'// 從localStorage中獲取token
function getLocalToken () {const token = window.localStorage.getItem('token')return token
}// 給實例添加一個setToken方法,用于登錄后將最新token動態添加到header,同時將token保存在localStorage中
instance.setToken = (token) => {instance.defaults.headers['X-Token'] = tokenwindow.localStorage.setItem('token', token)
}function refreshToken () {// instance是當前request.js中已創建的axios實例return instance.post('/refreshtoken').then(res => res.data)
}// 創建一個axios實例
const instance = axios.create({baseURL: '/api',timeout: 300000,headers: {'Content-Type': 'application/json','X-Token': getLocalToken() // headers塞token}
})// 是否正在刷新的標記
let isRefreshing = false
// 重試隊列,每一項將是一個待執行的函數形式
let requests = []instance.interceptors.response.use(response => {const { code } = response.dataif (code === 1234) {const config = response.configif (!isRefreshing) {isRefreshing = truereturn refreshToken().then(res => {const { token } = res.datainstance.setToken(token)config.headers['X-Token'] = tokenconfig.baseURL = ''// 已經刷新了token,將所有隊列中的請求進行重試requests.forEach(cb => cb(token))requests = []return instance(config)}).catch(res => {console.error('refreshtoken error =>', res)window.location.href = '/'}).finally(() => {isRefreshing = false})} else {// 正在刷新token,將返回一個未執行resolve的promisereturn new Promise((resolve) => {// 將resolve放進隊列,用一個函數形式來保存,等token刷新后直接執行requests.push((token) => {config.baseURL = ''config.headers['X-Token'] = tokenresolve(instance(config))})})}}return response
}, error => {return Promise.reject(error)
})export default instance

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/68016.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/68016.shtml
英文地址,請注明出處:http://en.pswp.cn/web/68016.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【DeepSeek系列】01 DeepSeek-V1 快速入門

1、DeepSeek簡介 2024年底,DeepSeek 相繼推出了其第一代推理大模型:DeepSeek-R1-Zero 和 DeepSeek-R1。 DeepSeek-R1-Zero 是一個通過大規模強化學習(RL)訓練的模型,訓練過程中沒有使用監督微調(SFT&…

基于LabVIEW的Modbus-RTU設備通信失敗問題分析與解決

在使用 LabVIEW 通過 Modbus-RTU 協議與工業設備進行通信時,可能遇到無法正常發送或接收指令的問題。常見原因包括協議參數配置錯誤、硬件連接問題、數據幀格式不正確等。本文以某 RGBW 控制器調光失敗為例,提出了一種通用的排查思路,幫助開發…

【初/高中生講機器學習】0. 本專欄 “食用” 指南——寫在一周年之際?

創建時間:2025-01-27 首發時間:2025-01-29 最后編輯時間:2025-01-29 作者:Geeker_LStar 你好呀~這里是 Geeker_LStar 的人工智能學習專欄,很高興遇見你~ 我是 Geeker_LStar,一名高一學生,熱愛計…

密云生活的初體驗

【】在《歲末隨筆之碎碎念》里,我通告了自己搬新家的事情。乙巳年開始,我慢慢與大家分享自己買房裝修以及在新家的居住體驗等情況。 跳過買房裝修的內容,今天先說說這三個月的生活體驗。 【白河】 潮白河是海河水系五大河之一,貫穿…

系統通解:超多視角理解

在科學研究和工程應用中,我們常常面臨各種復雜系統,需要精確描述其行為和變化規律。從物理世界的運動現象,到化學反應的進程,再到材料在受力時的響應,這些系統的行為往往由一系列數學方程來刻畫。通解,正是…

Python爬蟲:1藥城店鋪爬蟲(完整代碼)

??????????歡迎來到我的博客?????????? 🐴作者:秋無之地 🐴簡介:CSDN爬蟲、后端、大數據領域創作者。目前從事python爬蟲、后端和大數據等相關工作,主要擅長領域有:爬蟲、后端、大數據…

openwebui入門

1 簡介 ?Open WebUI?(網址是openwebui.com)是一個高度可擴展、功能強大且用戶友好的自托管Web用戶界面,專為完全離線操作設計,編程語言是python。它支持對接Ollama和OpenAI兼容的API的大模型。? Open WebUI?在架構上是一種中…

Day36-【13003】短文,數組的行主序方式,矩陣的壓縮存儲,對稱、三角、稀疏矩陣和三元組線性表,廣義表求長度、深度、表頭、表尾等

文章目錄 本次課程內容第四章 數組、廣義表和串第一節 數組及廣義表數組的基本操作數組的順序存儲方式-借用矩陣行列式概念二維數組C語言對應的函數-通常行主序方式 矩陣的壓縮存儲對稱矩陣和三角矩陣壓縮存儲后,采用不同的映射函數稀疏矩陣-可以構成三元組線性表三…

Android原生開發入門

1. 資源地址 Android官方教程Android參考手冊 2. 必看基礎模塊 應用基礎知識View 綁定 :綁定相當于Qt中的ui文件生成界面代碼的機制,Qt中的ucc會自動將ui文件編譯成ui_xxxx.h文件,Android開發中也一樣。 Android中自動生成的代碼在&#x…

3-Not_only_base/2018網鼎杯

3-Not_only_base 打開code MCJIJSGKPZZYXZXRMUW3YZG3ZZG3HQHCUS 分析: 首先看題知道解密過程中肯定有base解密。 知識點1: Base64字符集: 包含大小寫字母(A-Z、a-z)、數字(0-9)以及兩個特殊字…

deepseek、qwen等多種模型本地化部署

想要在本地部署deepseek、qwen等模型其實很簡單,快跟著小編一起部署吧 1 環境搭建 1.1下載安裝環境 首先我們需要搭建一個環境ollama,下載地址如下 :Ollama 點擊Download 根據自己電腦的系統選擇對應版本下載即可 1.2 安裝環境(window為例) 可以直接點擊安裝包進行安…

02/06 軟件設計模式

目錄 一.創建型模式 抽象工廠 Abstract Factory 構建器 Builder 工廠方法 Factory Method 原型 Prototype 單例模式 Singleton 二.結構型模式 適配器模式 Adapter 橋接模式 Bridge 組合模式 Composite 裝飾者模式 Decorator 外觀模式 Facade 享元模式 Flyw…

Idea ? Maven 選項

Idea ? Maven 選項 1. 在 Idea 項?上右鍵2. 選中 Maven 選項 如果在創建 Spring/Spring Boot 項?時,Idea 右側沒有 Maven 選項,如下圖所示: 此時可以使?以下?式解決。 1. 在 Idea 項?上右鍵 2. 選中 Maven 選項 選中 Maven 之后&#…

企業百科和品牌百科創建技巧

很多人比較困惑,創建百科詞條需要注意哪些事情?為什么參考提交了權威新聞參考資料還是沒有通過,下面小馬識途營銷顧問就為大家解答疑惑: 1、品牌詞以及企業詞提交 1)如果沒有詞條,我們可以通過平臺提供的急…

用Deepseek做EXCLE文件對比

背景是我想對比兩個PO系統里的一個消息映射,EDI接口的mapping有多復雜懂的都懂,它還不支持跨系統版本對比,所以我費半天勁裝NWDS,導出MM到excle,然后問題來了,我需要對比兩個excel文件里的內容,…

Agent開發注意事項

這里寫自定義目錄標題 llm應用開發什么是Agent?Agent1:工作流Agent2:自主AgentLLM如何擁有自主規劃能力? Tool 參考: llm應用開發 llm工程師需要具備以下能力: [] 軟件工程技能:將各個組件組裝在一起 [] 算法能力&am…

OpenCV:圖像輪廓

目錄 簡述 1. 什么是圖像輪廓? 2. 查找圖像輪廓 2.1 接口定義 2.2 參數說明 2.3 代碼示例 2.4 運行結果 3. 繪制圖像輪廓 3.1 接口定義 3.2 參數說明 3.3 代碼示例 3.4 運行結果 4. 計算輪廓周長 5. 計算輪廓面積 6. 示例:計算圖像輪廓的面…

在Mac mini M4上部署DeepSeek R1本地大模型

在Mac mini M4上部署DeepSeek R1本地大模型 安裝ollama 本地部署,我們可以通過Ollama來進行安裝 Ollama 官方版:【點擊前往】 Web UI 控制端【點擊安裝】 如何在MacOS上更換Ollama的模型位置 默認安裝時,OLLAMA_MODELS 位置在"~/.o…

CVPR | CNN融合注意力機制,蕪湖起飛!

**標題:**On the Integration of Self-Attention and Convolution **論文鏈接:**https://arxiv.org/pdf/2111.14556 **代碼鏈接:**https://github.com/LeapLabTHU/ACmix 創新點 1. 揭示卷積和自注意力的內在聯系 文章通過重新分解卷積和自…

module ‘matplotlib.cm‘ has no attribute ‘get_cmap‘

目錄 解決方法1: 解決方法2,新版api改了: module matplotlib.cm has no attribute get_cmap 報錯代碼: cmap matplotlib.cm.get_cmap(Oranges) 解決方法1: pip install matplotlib3.7.3 解決方法2,新版…