Apollo Client Error Link:JWT Cookie过期后刷新并重试请求
需求描述
我需要在服务器返回未授权错误(JWT Cookie过期导致)时,自动刷新JWT Cookie。具体逻辑是:当错误响应到达errorLink后,发送一个刷新认证Cookie的mutation请求,等刷新请求成功返回后,再重试最初的查询或mutation。我仅使用HttpOnly Cookie存储token,不依赖本地存储,Cookie本身功能正常,但始终无法实现这个刷新重试逻辑,求可行的实现方案或示例代码。
我的尝试代码
let tokenRefreshPromise = Promise.resolve(); let isRefreshing; const errorLink = onError( ({ graphQLErrors, networkError, operation, forward }) => { if (graphQLErrors) { console.log(graphQLErrors); graphQLErrors.forEach(({ message, locations, path }) => { if ( message === "You do not have permission to perform this action" && !isRefreshing ) { isRefreshing = true; tokenRefreshPromise = fetch(process.env.REACT_APP_GRAPH_URL, { method: "POST", mode: "cors", credentials: "include", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ query: `mutation{ refreshToken { payload refreshExpiresIn } }`, }), }); // console.log(tokenRefreshPromise); tokenRefreshPromise.then(() => (isRefreshing = false)); } return fromPromise(tokenRefreshPromise).flatMap(() => forward(operation) ); }); } if (networkError) { console.log(`[Network error]: ${networkError}`); } } );
问题分析与修正方案
你的代码存在几个关键问题:
- 在
graphQLErrors.forEach中直接返回重试逻辑,forEach无法正确处理返回值,会导致每个错误都触发重试,逻辑混乱 - 未处理刷新请求失败的场景,比如刷新token也过期的情况
- 重试逻辑没有正确限定范围,应该只在遇到目标未授权错误时触发
以下是修正后的完整实现:
import { onError } from "@apollo/client/link/error"; import { fromPromise } from "@apollo/client"; let isRefreshing = false; let pendingOperations = []; // 处理等待中的请求,刷新完成后统一重试 const resolvePendingOperations = () => { pendingOperations.forEach(operation => operation()); pendingOperations = []; }; const errorLink = onError(({ graphQLErrors, operation, forward }) => { if (graphQLErrors) { const unauthorizedError = graphQLErrors.find( err => err.message === "You do not have permission to perform this action" ); if (unauthorizedError) { if (!isRefreshing) { isRefreshing = true; // 发送刷新token的mutation请求 const refreshPromise = fetch(process.env.REACT_APP_GRAPH_URL, { method: "POST", mode: "cors", credentials: "include", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ query: `mutation { refreshToken { payload refreshExpiresIn } }`, }), }) .then(response => { if (!response.ok) throw new Error("刷新token失败"); return response.json(); }) .catch(err => { // 刷新失败时清空等待队列,避免请求无限挂起 pendingOperations = []; throw err; }) .finally(() => { isRefreshing = false; resolvePendingOperations(); }); // 刷新完成后重试当前请求 return fromPromise(refreshPromise).flatMap(() => forward(operation)); } else { // 已有刷新请求在处理,将当前请求加入等待队列 return fromPromise( new Promise(resolve => { pendingOperations.push(() => resolve(forward(operation))); }) ); } } } // 非授权错误直接返回,不做处理 return; });
关键逻辑说明
- 避免并发刷新:用
isRefreshing标记刷新状态,同一时间只发起一个刷新请求,防止重复调用刷新接口 - 等待队列管理:当已有刷新请求时,将后续需要重试的请求加入队列,刷新完成后统一触发重试
- 错误边界处理:刷新失败时清空等待队列,避免请求无限等待
- 精准触发逻辑:仅匹配到目标未授权错误时才执行刷新流程,其他错误直接放行
内容的提问来源于stack exchange,提问作者Gurhan Goktas
相关产品推荐
相关产品推荐

