项目开发中,我们在请求数据的时候经常会遇到 token 失效,无法请求数据的问题。 遇到这类问题,一般的处理方法就是跳转到登录页面重新登录获取token。如果忽视用户的是用心情,这样处理也未尝不可。
但这并不是我想要的效果。作为一个有理想的前端我们应该有更高的要求。 我期望当接口过期时,重新请求一个有效的token,并使用新token,重试上次失败的请求。
解决思路
本文以 axios 为例,因此我们需要在请求的拦截器 axios.interceptors.response.use() 上做文章。我的后端没有在接口中返回状态码,因此我实在拦截器的 error 的处理函数中做处理
具体思路:
- 在 axios.interceptors.response.use() 拦截器 error 处理函数中判断状态码 (error.request.status) 是否为 401
- 如果为 401 记录当前请求的 config = error.config 并且 return 一个请求 updateToken(获取新token的请求)
- 为了避免 在请求新的 token 过程中有新的请求进来,再次重新请求 token,导致新的 token 失效,我们需要一个标记变量来锁定,确保不会多次请求新的 token 造成影响。
- 设定一个队列,将开始更新 token 开始之后,完成之前的所有请求以一个待执行的函数形式存放在其中
- 在 updateToken 的回调函数中更新缓存的 token, 并将即将重试的config中的token 更新, 同时 为了避免缓存的影响,将请求的地址 增加一个时间戳参数
- 启用队列中待重试函数,启用完成后队列置空。 重试当前 config 实例
项目中的token是存在localStorage中的。request.js基本结果:如下
/**
* 方法说明,
* setToken() 在 localStorage 中存 token 的方法
* getToken() 获取 localStorage 中 token 的方法
* removeToken() 清除 localStorage 中 token 的方法
* updateToken() 获取新的 token 的请求
* */
// 1.声明实例
const service = axios.create({
timeout: 50000 // request timeout
})
// 是否正在刷新的标记
let isRefreshing = false
// 重试队列
let requests = []
service.interceptors.response.use(
(response) => {
return response
},
(error) => {
if (error.request.status === 401) {
// 记录实例
const config = error.config
// 判断是否正在更新 token
if(!isRefreshing) {
// 改变标记状态
isRefreshing = true
// 请求 token 的函数前一定要 return 否则重试成功的数据,
// 不会返回到请求数据函数的 response 中
return updateToken().then(res => {
setToken(res.data.access_token)
// 重试队列中存储请求
requests.forEach(cb => cb())
// 清空队列
requests = []
return service(config)
}).catch(()=> {
// 请求新的 token 失败 跳转登录页
}).finally(() => {
// 请求回 token 处理完成 一定要改变标记状态 否则会一直重新请求 token
isRefreshing = false
})
} else {
return new Promise((resolve) => {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
requests.push(() => {
// // 给url 增加时间戳 避免请求缓存影响
if( config.url.indexOf('?') > -1) {
config.url = config.url + '&n='+ new Date().getTime()
} else {
config.url = config.url + '?n='+ new Date().getTime()
}
config.headers['Authorization'] = getToken()
resolve(service(config))
})
})
}
} else {
return Promise.reject(error)
}
})
export default service
本文实现参考: axios如何利用promise无痛刷新token