Nuxt页面与组件同时使用fetch导致refresh token接口重复调用问题
问题根因
- 你当前场景下main页面和
<Categories/>组件的请求是并行发起的,两个请求同时检测到access token过期,同时触发了refresh token逻辑。你现有的refresh逻辑没有做并发控制,所以会触发两次refresh调用。 - 你把组件请求放到
mounted中依旧复现的原因是:客户端渲染阶段两个请求仍然是近似同时发起,依旧存在并发冲突,和使用fetch还是mounted钩子无关。 - 单请求页面不会出现该问题,是因为没有并发的过期请求触发多次refresh。
解决思路
方案1:改造refresh token公共逻辑(最推荐)
在你的请求拦截器中添加刷新锁和请求排队逻辑,不管有多少个并发过期请求,始终只调用1次refresh接口,不需要修改任何业务代码,组件可以保留自己的fetch逻辑。
示例代码(以axios拦截器为例,fetch封装同理):
// 全局变量,标记是否正在刷新token let isRefreshing = false // 等待刷新完成后重发的请求队列 let requestQueue = [] // 请求拦截器逻辑 axios.interceptors.request.use(config => { const accessToken = getAccessToken() if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}` } return config }) // 响应拦截器逻辑 axios.interceptors.response.use( response => response, async error => { const originalRequest = error.config // 判断是token过期错误 if (error.response.status === 401 && !originalRequest._retry) { if (isRefreshing) { // 已经在刷新token,把当前请求加入队列等待 return new Promise(resolve => { requestQueue.push(token => { originalRequest.headers.Authorization = `Bearer ${token}` resolve(axios(originalRequest)) }) }) } originalRequest._retry = true isRefreshing = true try { // 调用refresh接口获取新token const newToken = await refreshToken() saveNewToken(newToken) // 重发队列里的所有请求 requestQueue.forEach(cb => cb(newToken)) requestQueue = [] // 重发当前请求 originalRequest.headers.Authorization = `Bearer ${newToken}` return axios(originalRequest) } catch (err) { // 刷新失败,跳转到登录页 location.href = '/login' return Promise.reject(err) } finally { isRefreshing = false } } return Promise.reject(error) } )
方案2:调整请求触发时机,避免并发
如果你暂时不想改造公共请求逻辑,可以手动错开两个请求的执行时间:
- 在main页面定义一个
isMainRequestDone的响应式变量,初始值为false - 等main页面的fetch逻辑执行完成后,把
isMainRequestDone设为true - 将该变量作为props传给
<Categories/>组件,组件内监听该变量,当变为true时再发起自己的数据请求,从业务层面避免两个请求同时检测到token过期。
方案3:利用Nuxt的fetch调度逻辑
你可以给<Categories/>组件设置fetchOnServer: false,同时在页面的fetch完成后手动调用组件的$fetch方法:
<!-- main页面 --> <template> <Categories ref="categories" /> </template> <script> export default { async fetch() { await this.$store.dispatch( "pub/getalll", "/api/u/all?limit=" + this.pageSize + "&page=" + this.currentPage ); // main页面请求完成后再调用组件的fetch this.$nextTick(() => { this.$refs.categories.$fetch() }) } } </script>
对应的<Categories/>组件设置fetchOnServer: false,避免服务端提前执行fetch。
内容的提问来源于stack exchange,提问作者eMan_8888
相关产品推荐
相关产品推荐

