如何等待所有并行Promise完成?前端Token刷新去重优化
解决并发请求下重复Token刷新的优雅方案
核心思路是全局共享一个刷新Token的Promise实例,让所有并发请求复用同一个刷新操作,彻底避免重复发起刷新请求,同时不需要依赖轮询式的等待逻辑。
实现步骤
- 全局维护一个变量存储当前正在进行的刷新Promise,初始为
null - 当检测到Token过期需要刷新时:
- 如果已有刷新Promise存在,直接返回该Promise,让所有并发请求等待同一结果
- 如果没有,则发起刷新请求,将Promise赋值给全局变量,请求完成后重置该变量
代码示例
// 全局变量:存储当前正在进行的Token刷新Promise let ongoingRefresh = null; async function refreshAccessToken() { // 复用已存在的刷新请求 if (ongoingRefresh) { return ongoingRefresh; } try { // 发起刷新请求,将Promise赋值给全局变量 ongoingRefresh = fetch('/api/refresh-token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('refreshToken')}` } }).then(async res => { if (!res.ok) throw new Error('Token刷新失败'); const { accessToken } = await res.json(); localStorage.setItem('accessToken', accessToken); return accessToken; }).finally(() => { // 无论成功失败,重置全局变量,允许后续刷新请求 ongoingRefresh = null; }); return ongoingRefresh; } catch (err) { // 处理刷新失败逻辑,比如跳转登录页 console.error('刷新Token出错:', err); localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; throw err; } } // 请求拦截器示例(适配Axios或原生fetch) async function authorizedRequest(url, options = {}) { let accessToken = localStorage.getItem('accessToken'); const isExpired = checkTokenExpiration(accessToken); // 自行实现Token过期判断 if (isExpired) { // 等待刷新完成,获取新Token accessToken = await refreshAccessToken(); } // 携带Token发起请求 return fetch(url, { ...options, headers: { ...options.headers, 'Authorization': `Bearer ${accessToken}` } }); } // 辅助函数:判断Token是否过期 function checkTokenExpiration(token) { if (!token) return true; const payload = JSON.parse(atob(token.split('.')[1])); return Date.now() >= payload.exp * 1000; }
方案优势
- 完全基于Promise异步特性,没有
setTimeout轮询的hack逻辑,代码更直观易维护 - 所有并发请求共享同一个刷新操作,不会重复发起刷新请求,避免资源浪费
- 自动处理刷新成功/失败后的状态重置,不影响后续请求
关于yield的疑问
yield是Generator函数的语法,虽然配合co库也能实现异步流程控制,但现在async/await已经是JavaScript异步编程的标准方案,语法更简洁直观,完全能满足需求,不需要再使用yield的方式。
内容的提问来源于stack exchange,提问作者kguzek
相关产品推荐
相关产品推荐

