javascript Axios Interceptor在处理请求之前不会等待await函数调用完成

n6lpvg4x  于 2023-05-16  发布在  Java
关注(0)|答案(2)|浏览(126)

我有这个拦截器代码,用于使用Axios发出的所有请求。这里的instance是Axios示例。

instance.interceptors.request.use(async (config) => {
  const tokens = JSON.parse(localStorage.getItem('users'));
  if (!tokens) {
    return config;
  }
  const accessTokenValid = Date.now() <= (tokens?.expirationTime ?? Date.now());
  if (accessTokenValid) {
    return config;
  }
  const refreshToken = tokens.refreshToken;
  if (!refreshToken) { 
    return config;
  }

  const response = await fetch(`${some-endpoint}/auth/refresh/token?token=${refreshToken}`);
  const newTokens = (await response.json()).data.data;

  localStorage.setItem('users', JSON.stringify(
    {
      accessToken: newTokens.access_token,
      expirationTime: Date.now() + parseInt(JSON.parse(newTokens.expires_in)) * 1000 - 600 * 1000,
      refreshToken: newTokens.refresh_token,
    }
  ));

  config.headers.Authorization = `Bearer ${newTokens.access_token}`;

  return config;
}, (error) => {
  console.error(error);
});

然后,instance将像这样使用。

return instance.get(
      "/users/" + id,
      options
    );

options是这样的

{
          headers: {
            Authorization: `Bearer ${session.accessToken}`,
          },
        }

上面代码的问题是令牌过期后的第一个请求总是失败。然后,localStorage.users中的值将被更新,并且第二个请求将成功,因为该值已被新的访问令牌替换。
我不想在响应中使用拦截器来重试原始请求。我想根据请求处理令牌刷新。经过调查,发现HTTP请求发生的顺序如下所示。

  • 对端点users的HTTP请求
  • 然后,向端点发送HTTP请求以刷新令牌

这告诉我请求拦截器中的async/await部分没有工作。我怎样才能使HTTP请求在运行HTTP请求本身之前必须等待拦截器首先完成运行(包括等待任何异步函数)?

gwbalxhn

gwbalxhn1#

在这里我会做一些不同的事情...
1.* 始终 * 将添加Authorization头的责任委托给拦截器。您的消费代码不需要知道它
1.对需要令牌刷新的请求进行排队,以避免多次刷新请求
1.对所有请求使用Fetch或Axios。混合它们只会导致更大的爆炸半径和更困难的测试

// Create instance
const instance = axios.create({ baseURL: "https://echo.zuplo.io/" });

let refreshTokenPromise; // used to queue up requests

// Add interceptor
instance.interceptors.request.use(async (config) => {
  const tokens = JSON.parse(localStorage.getItem('users'));
  if (!tokens) {
    console.warn("No tokens");
    return config;
  }
  const accessTokenValid = Date.now() <= (tokens.expirationTime ?? Date.now());
  if (accessTokenValid) {
    console.log("Token still valid");
    
    // Now add the header
    config.headers.Authorization = `Bearer ${tokens.accessToken}`;
    return config;
  }
  const refreshToken = tokens.refreshToken;
  if (!refreshToken) { 
    console.warn("No refresh token");
    return config;
  }
  
  // Check for in-flight refresh requests and start one if required
  refreshTokenPromise ??= getRefreshedTokens(refreshToken);
  
  const newTokens = await refreshTokenPromise;
  refreshTokenPromise = null; // clean in-flight state

  localStorage.setItem('users', JSON.stringify(newTokens));

  config.headers.Authorization = `Bearer ${newTokens.access_token}`;

  return config;
});

// Auth requests
const authApi = axios.create(/* baseURL, headers, etc */);
const getRefreshedTokens = async (token) => {
  console.log("Refresh auth token");
  // return (await authApi.get("/auth/refresh/token", { params: { token } })).data.data.data;
  
  // mock data instead
  return delay({
    access_token: "new_access_token",
    expires_in: new Date(Date.now() + 3600000), // +1 hour
    refresh_token: "new_refresh_token",
  });
};

// Mocks (localStorage doesn't work in Snippets)
const localStorage = {s:{},setItem(k,v){this.s[k]=v},getItem(k){return this.s[k]??null}};
const delay = (v,d=2000)=>new Promise(r=>setTimeout(r,d,v));

// Demo setup
localStorage.setItem("users", JSON.stringify({
  accessToken: "old_access_token",
  expirationTime: new Date(2020, 0, 1), // very expired
  refreshToken: "refresh_token",
}));

// Simulate simultaneous requests
[123, 456, 789].forEach(async (id) => {
  console.log("Getting user", id);
  const {
    data: {
      url,
      headers: {
        authorization
      }
    }
  } = await instance.get(`/users/${id}`);
  console.log("user response:", { url, authorization });
});
.as-console-wrapper { max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.4.0/axios.min.js"></script>

您可以看到,序列首先解析刷新令牌,并且在使用更新的访问令牌发出 user 请求之前只解析一次。

cld4siwp

cld4siwp2#

这里出现的问题是因为axios在拦截器有机会刷新令牌之前发送了原始请求。您需要做的是防止发送原始请求,直到令牌被刷新。
一种方法是显式地暂停原始请求,直到刷新完成。这可以通过在刷新令牌时创建传入请求的队列,然后在刷新令牌后解析它们来完成。

相关问题