You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apollo Client Error Link:JWT Cookie过期后刷新并重试请求

JWT Cookie过期时通过Apollo errorLink自动刷新并重试请求

需求描述

我需要在服务器返回未授权错误(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;
});

关键逻辑说明

  1. 避免并发刷新:用isRefreshing标记刷新状态,同一时间只发起一个刷新请求,防止重复调用刷新接口
  2. 等待队列管理:当已有刷新请求时,将后续需要重试的请求加入队列,刷新完成后统一触发重试
  3. 错误边界处理:刷新失败时清空等待队列,避免请求无限等待
  4. 精准触发逻辑:仅匹配到目标未授权错误时才执行刷新流程,其他错误直接放行

内容的提问来源于stack exchange,提问作者Gurhan Goktas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 00:02:23