Vue 3 composable for Genesys Cloud token refresh during long-running batch jobs

Could someone explain why my access token expires mid-execution even though I’ve implemented the standard refresh logic?

I’m building a supervisor dashboard in Vue 3 that pulls queue stats via the Genesys Cloud Statistics API. It’s a heavy operation. We’re pulling metrics for 50+ queues in parallel. The initial OAuth token lasts 1800 seconds. My job takes about 25 minutes. I know the token will expire. I’ve set up a composable to handle the refresh. I store the refresh token in secure storage. When I detect a 401 Unauthorized response, I trigger the refresh. But the batch fails anyway. Here’s the relevant code snippet:

const refreshAccessToken = async () => {
 const refreshToken = getRefreshToken();
 const response = await fetch(`${baseUrl}/oauth/token`, {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify({
 grant_type: 'refresh_token',
 refresh_token: refreshToken,
 client_id: clientId
 })
 });
 const data = await response.json();
 if (response.ok) {
 setAccessToken(data.access_token);
 return data.access_token;
 }
 throw new Error('Token refresh failed');
};

const apiCall = async (endpoint) => {
 let token = getAccessToken();
 let response = await fetch(`${baseUrl}${endpoint}`, {
 headers: { Authorization: `Bearer ${token}` }
 });
 if (response.status === 401) {
 token = await refreshAccessToken();
 response = await fetch(`${baseUrl}${endpoint}`, {
 headers: { Authorization: `Bearer ${token}` }
 });
 }
 return response.json();
};

The issue is concurrency. I fire off 50 requests. They all start with the same token. Halfway through, the token expires. Multiple requests hit the 401. They all try to refresh simultaneously. The first one succeeds. The others fail because the refresh token is single-use or gets invalidated by the race condition. I end up with a mix of successful data and failed requests. I need a way to queue the refresh or ensure only one refresh happens at a time. How do you handle this in Vue 3 without blocking the UI? I’m using the composition API. I don’t want to use a global store if I can avoid it. Is there a pattern for this? I’ve seen some people use a mutex or a promise queue. But I’m not sure how to implement that cleanly in Vue. Any code examples would be great. I’m stuck on this for days.