能力:防重复请求(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);
});正确的统一处理出口有两个:
notify出口(推荐做 toast/提示):block 命中时会触发notify,无需业务侧 catch:
javascript
setupRequestGuard(axios, {
notify: (payload) => {
if (payload.capability === 'duplicate') Toast.show(payload.message);
}
});- 请求级
.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 返回 undefined、null 或空字符串时回退到默认稳定序列化;返回 0、false 会被转成字符串继续作为 key。
配置项
请求级配置(requestGuard.duplicate)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| enabled | boolean | true | 本次请求是否启用防重 |
| strategy | string | 继承全局 | 策略:block / reuse |
| compareFields | string[] | 继承全局 | 判重比较字段 |
| compareHeaders | string[] | [] | 额外参与判重的 header |
| maxCacheSize | number | 继承全局 | 单策略在途记录硬上限,满额时新 key 拒绝 |
| message | string | 继承全局 | 命中时提示文案 |
| showToast | boolean | 继承全局 | 是否触发 notify |
| keyGenerator | function | 继承全局 | 自定义 key 生成函数 |
| idempotencyKey | string / function | 继承全局 | 幂等 key 生成规则 |
全局默认配置(defaults.duplicate)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| enabled | boolean | true | 能力总开关 |
| strategy | string | 'block' | 默认策略 |
| compareFields | string[] | ['method','baseURL','url','params','data'] | 判重比较字段 |
| excludeMethods | string[] | ['GET','OPTIONS','HEAD'] | 规则匹配排除的方法 |
| maxCacheSize | number | 100 | 单策略在途记录硬上限,满额时新 key 拒绝 |
| message | string | '请勿重复提交' | 默认提示文案 |
| showToast | boolean | true | 是否触发 notify |
| keyGenerator | function | null | 自定义 key 生成函数 |
| idempotencyKey | boolean / function | true | 幂等 key 生成规则 |
| responseResolver | function | (response) => response | 复用结果返回给等待方前转换响应 |
| errorResolver | function | (error) => error | 复用结果返回给等待方前转换错误 |
showToast 与 notify
showToast: true 只是"允许触发 notify",真正的 UI 提示由你配置的 notify 回调负责。若没配 notify,重复请求仍会被正常拦截(开发环境默认 logger 会在控制台显示),但不会有任何 toast。
错误类型
| 错误名 | 触发时机 |
|---|---|
RequestGuardBlockedError | block 策略命中重复请求 |
RequestGuardCancelledError | reuse 等待方被 clearState() 主动取消 |
RequestGuardCacheEvictedError | duplicate 容量满拒绝新 key,或后台状态维护清理滞留 reuse 记录时通知等待方结束 |
更多错误处理见 错误类型。
