前端接口失败重试怎么做才不会重复请求?

码农梓怡 阅读 55

我在做用户登录功能,网络不稳定时想加个重试机制,但发现有时候会连续发好几次一样的请求,比如用户点一次登录,结果因为重试发了三次。我试过用 axios 的拦截器加 retry 逻辑,但没控制好并发。

现在用的是这种简单写法:

const request = (url, options) => {
  let retries = 0;
  const maxRetries = 3;
  
  const attempt = () => {
    return fetch(url, options).catch(err => {
      if (retries < maxRetries) {
        retries++;
        return attempt();
      }
      throw err;
    });
  };
  
  return attempt();
};

但这样每次调用 request() 都会新建一个 retry 实例,如果用户快速点击多次,就会同时跑好几个重试流程。有没有办法在组件层面或者请求层面对同一个请求做去重 + 重试?

我来解答 赞 11 收藏
二维码
手机扫码查看
2 条解答
端木明阳
这个问题的本质是:相同参数的请求正在pending时,后续的相同请求应该复用而不是重新发起。

简单有效的做法是用一个Map来管理pending状态的请求:

const pendingRequests = new Map();

const request = async (url, options) => {
// 用url+请求体生成唯一key
const key = ${url}:${JSON.stringify(options?.body || '')};

// 如果已经有相同请求在进行了,直接返回那个Promise
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}

const maxRetries = 3;
let retries = 0;

const execute = async () => {
try {
const result = await fetch(url, options);
return result;
} catch (err) {
if (retries < maxRetries) {
retries++;
// 稍微间隔一下再重试,别太频繁
await new Promise(r => setTimeout(r, 1000 * retries));
return execute();
}
throw err;
}
};

// 把当前请求的Promise存入Map
const promise = execute().finally(() => {
// 请求结束后从Map中移除
pendingRequests.delete(key);
});

pendingRequests.set(key, promise);
return promise;
};


这样处理后,假设用户疯狂点击登录按钮,只有第一个请求会真正发出去,后面的请求会直接复用第一个请求的Promise,等第一个请求重试完或者成功失败后,所有点击都会拿到同样的结果。

如果想在组件层面更细粒度控制,可以在按钮点击时加个loading状态,用户点第一次就把按钮disabled掉,防止继续点击。这个配合上面的请求层去重,双重保险,基本就稳了。

还有个进阶思路是用AbortController,如果你想实现"取消之前的请求,只保留最后一次"这种行为,而不是"只发第一个请求"。看具体业务需求选用即可。
点赞
2026-03-13 11:24
Mr.玉娅
Mr.玉娅 Lv1
搞个全局 Map 缓存请求的 Promise,发之前先看缓存里有没有,有就直接返回,没的话发完请求记得删掉缓存。这样不管用户点多快,底层永远只有一个请求在跑,重试逻辑放在那个 Promise 里就行了。

const pendingRequests = new Map();

const request = async (url, options = {}) => {
const key = url + (options.method || 'GET') + JSON.stringify(options.body || {});

if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}

const maxRetries = 3;
let retries = 0;

const attempt = async () => {
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(res.status);
return res;
} catch (err) {
if (retries < maxRetries) {
retries++;
console.log(重试第 ${retries} 次);
return attempt();
}
throw err;
}
};

const promise = attempt().finally(() => {
pendingRequests.delete(key);
});

pendingRequests.set(key, promise);
return promise;
};
点赞 1
2026-03-04 03:05