Skip to content

能力:防重复请求(Duplicate)

防止同一请求被重复发送。表单提交用 block 阻断,数据查询用 reuse 复用。

策略选择

策略首次请求重复请求适合场景
block(阻断)正常发送直接拒绝,抛出 RequestGuardBlockedError表单提交、下单、支付
reuse(复用)正常发送等待首次请求结果,共享响应列表查询、数据加载、配置查询

单业务场景配置

block 策略:重复请求直接报错

适合表单提交、创建订单、支付等写操作:

javascript
const submitOrder = async (orderData) => {
  const res = await axios.post('/api/order/submit', orderData, {
    requestGuard: {
      duplicate: {
        strategy: 'block',             // 阻断策略
        message: '订单提交中,请稍候'    // 用户看到的提示
      }
    }
  });
  return res.data;
};

// 用户快速双击按钮
submitOrder(data);  // ✅ 正常发送
submitOrder(data);  // ❌ 抛出 RequestGuardBlockedError,触发 notify 提示

reuse 策略:多个请求共享同一响应

适合列表查询、配置加载等读操作:

javascript
const fetchList = (params) => axios.get('/api/list', {
  params,
  requestGuard: {
    duplicate: {
      strategy: 'reuse',              // 复用策略
      compareFields: ['method', 'url', 'params']  // 按这些字段判断是否重复
    }
  }
});

// 组件 A 和组件 B 同时发起相同查询
const promiseA = fetchList({ page: 1 });
const promiseB = fetchList({ page: 1 });

// 实际只发出 1 次网络请求,两者拿到相同的响应
const [resA, resB] = await Promise.all([promiseA, promiseB]);
console.log(resA === resB);  // true — 共享同一响应

短路错误不经过响应拦截器

重要

block 命中时请求根本没发出去(在请求真正发出前就被拦截),所以你写在 axios.interceptors.response.use(null, onRejected) 里的统一错误处理不会执行。这对 duplicate block、以及 circuitBreaker 熔断打开两种"请求未发出"的短路错误都成立。

javascript
// ❌ 不可靠:短路错误不会进入这里
axios.interceptors.response.use(null, (error) => {
  if (error.name === 'RequestGuardBlockedError') { Toast.show(error.message); }
  return Promise.reject(error);
});

正确的统一处理出口有两个:

  1. notify 出口(推荐做 toast/提示):block 命中时会触发 notify,无需业务侧 catch:
javascript
setupRequestGuard(axios, {
  notify: (payload) => {
    if (payload.capability === 'duplicate') Toast.show(payload.message);
  }
});
  1. 请求级 .catch(需要按错误类型分支时):短路错误会从发起调用的 Promise 直接 reject 出来,在调用处用 error.name 判别:
javascript
try {
  await axios.post('/api/order', data);
} catch (error) {
  if (error.name === 'RequestGuardBlockedError') Toast.show(error.message);
}

自定义 Key 生成

javascript
requestGuard: {
  duplicate: {
    // 方式 1:指定比较字段(从请求 config 中取这些字段拼接成 key)
    compareFields: ['method', 'url', 'data'],

    // 方式 2:自定义生成函数(完全自主决定 key 格式)
    keyGenerator(config, options) {
      return `order:${config.data.clientOrderId}`;
    }
  }
}

TIP

keyGenerator 返回 undefinednull 或空字符串时回退到默认稳定序列化;返回 0false 会被转成字符串继续作为 key。

配置项

请求级配置(requestGuard.duplicate

配置项类型默认值说明
enabledbooleantrue本次请求是否启用防重
strategystring继承全局策略:block / reuse
compareFieldsstring[]继承全局判重比较字段
compareHeadersstring[][]额外参与判重的 header
maxCacheSizenumber继承全局单策略在途记录硬上限,满额时新 key 拒绝
messagestring继承全局命中时提示文案
showToastboolean继承全局是否触发 notify
keyGeneratorfunction继承全局自定义 key 生成函数
idempotencyKeystring / function继承全局幂等 key 生成规则

全局默认配置(defaults.duplicate

配置项类型默认值说明
enabledbooleantrue能力总开关
strategystring'block'默认策略
compareFieldsstring[]['method','baseURL','url','params','data']判重比较字段
excludeMethodsstring[]['GET','OPTIONS','HEAD']规则匹配排除的方法
maxCacheSizenumber100单策略在途记录硬上限,满额时新 key 拒绝
messagestring'请勿重复提交'默认提示文案
showToastbooleantrue是否触发 notify
keyGeneratorfunctionnull自定义 key 生成函数
idempotencyKeyboolean / functiontrue幂等 key 生成规则
responseResolverfunction(response) => response复用结果返回给等待方前转换响应
errorResolverfunction(error) => error复用结果返回给等待方前转换错误

showToast 与 notify

showToast: true 只是"允许触发 notify",真正的 UI 提示由你配置的 notify 回调负责。若没配 notify,重复请求仍会被正常拦截(开发环境默认 logger 会在控制台显示),但不会有任何 toast。

错误类型

错误名触发时机
RequestGuardBlockedErrorblock 策略命中重复请求
RequestGuardCancelledErrorreuse 等待方被 clearState() 主动取消
RequestGuardCacheEvictedErrorduplicate 容量满拒绝新 key,或后台状态维护清理滞留 reuse 记录时通知等待方结束

更多错误处理见 错误类型

基于 MIT 许可发布