封装 axios 拦截器实现用户无感刷新 access_token
作者: 图恩分类: 编程开发阅读: 666字数: 10615发布时间: 2024-07-29 英文版详情 为了解决多个请求同时发起时刷新 `access_token` 的问题,需在代码中实现以下关键逻辑:
---
### **1. 优化 `requests` 数组的处理逻辑**
当多个请求同时发起时,需确保所有待重发的请求在 `access_token` 刷新后被正确执行。
```javascript
let isRefreshing = false;
let requests = []; // 存储待重发请求的数组
```
**关键逻辑:**
- 在 `refreshToken` 成功后,遍历 `requests` 数组,逐个执行回调函数。
- 刷新完成后清空数组,避免重复处理。
```javascript
instance.interceptors.response.use(
response => response,
error => {
if (!error.response) return Promise.reject(error);
if (error.response.status === 401 && !error.config.url.includes('/auth/refresh')) {
const { config } = error;
if (!isRefreshing) {
isRefreshing = true;
return refreshToken().then(res => {
const { access_token } = res.data;
setToken(access_token);
config.headers.Authorization = `Bearer ${access_token}`;
// 执行所有待重发请求
requests.forEach(cb => cb(access_token));
requests = [];
return instance(config);
}).catch(err => Promise.reject(err)).finally(() => {
isRefreshing = false;
});
} else {
// 保存未执行 resolve 的 Promise
return new Promise(resolve => {
requests.push(token => {
config.headers.Authorization = `Bearer ${token}`;
resolve(instance(config));
});
});
}
}
return Promise.reject(error);
}
);
```
---
### **2. 确保所有请求在刷新后被正确执行**
在 `refreshToken` 成功后,需确保所有待重发的请求回调函数被正确执行:
```javascript
requests.forEach(cb => cb(access_token));
requests = [];
```
**说明:**
- `requests` 数组中的每个元素是回调函数(如 `cb(access_token)`),在刷新后逐个执行。
- 刷新完成后清空数组,避免重复处理。
---
### **3. 处理刷新失败时的未执行 Promise**
当 `refreshToken` 失败时,需将未执行 `resolve` 的 Promise 保存到 `requests` 数组中,以便后续处理:
```javascript
return new Promise(resolve => {
requests.push(token => {
config.headers.Authorization = `Bearer ${token}`;
resolve(instance(config));
});
});
```
**关键点:**
- `resolve` 函数被保存到 `requests` 数组中,待 `access_token` 更新后执行。
- 当刷新成功时,数组中的所有回调函数被调用,确保所有请求重发。
---
### **4. 防止多次刷新 `access_token`**
通过 `isRefreshing` 标志控制刷新状态,避免在刷新期间同时发起新请求:
```javascript
if (!isRefreshing) {
isRefreshing = true;
return refreshToken().then(...);
}
```
**说明:**
- 如果正在刷新 `access_token`,新请求直接返回未执行 `resolve` 的 Promise,避免重复处理。
---
### **最终优化后的代码**
```javascript
// request.js
import axios from 'axios';
import { getToken, setToken, getRefreshToken } from './auth';
const instance = axios.create({
baseURL: process.env.GATSBY_API_URL,
timeout: 30000,
headers: { 'Content-Type': 'application/json' }
});
let isRefreshing = false;
let requests = [];
instance.interceptors.response.use(
response => response,
error => {
if (!error.response) return Promise.reject(error);
if (error.response.status === 401 && !error.config.url.includes('/auth/refresh')) {
const { config } = error;
if (!isRefreshing) {
isRefreshing = true;
return refreshToken().then(res => {
const { access_token } = res.data;
setToken(access_token);
config.headers.Authorization = `Bearer ${access_token}`;
requests.forEach(cb => cb(access_token));
requests = [];
return instance(config);
}).catch(err => Promise.reject(err)).finally(() => {
isRefreshing = false;
});
} else {
return new Promise(resolve => {
requests.push(token => {
config.headers.Authorization = `Bearer ${token}`;
resolve(instance(config));
});
});
}
}
return Promise.reject(error);
}
);
const setHeaderToken = (isNeedToken) => {
const accessToken = isNeedToken ? getToken() : null;
if (isNeedToken) {
instance.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
}
};
export const get = (url, params = {}, isNeedToken = false) => {
setHeaderToken(isNeedToken);
return instance({
method: 'get',
url,
params
});
};
export const post = (url, data, isNeedToken = false) => {
setHeaderToken(isNeedToken);
return instance({
method: 'post',
url,
data
});
};
```
---
### **总结**
- **关键逻辑**:通过 `requests` 数组保存待重发请求,确保所有请求在 `access_token` 更新后被正确执行。
- **防止重复刷新**:使用 `isRefreshing` 标志控制刷新状态,避免在刷新期间发起新请求。
- **处理失败情况**:将未执行 `resolve` 的 Promise 保存到数组中,确保后续请求被正确触发。
此方案可有效解决多请求同时发起时刷新 `access_token` 的问题,确保所有请求在令牌更新后被正确处理。
发表评论 (审核通过后显示评论):