引言
在大型团队的项目开发过程中,确保应用的健壮性和效率至关重要。特别是对于需要多人协作的项目,经常会出现一些重复性的问题,如表单的重复提交,这不仅可能导致资损,还会大大增加开发和测试的工作量。为了解决这一问题开发了 more-request
,一个基于 Axios 的请求管理包,专门设计来自动化处理请求的节流。
为什么提交按钮需要增加节流处理?
- 防止用户重复点击:用户可能因为网络延迟或不明显的UI反馈而多次点击提交按钮,导致同一请求被发送多次。
- 提高用户体验:节流处理可以防止过多的请求同时发送,从而防止应用变得缓慢或不响应,提高用户的整体体验。
- 减轻服务器负担:频繁的重复请求可能会对服务器造成不必要的负担,影响其处理其他用户或其他操作的能力。
重复发送为什么可能带来资损?
- 数据完整性问题:例如,在电子商务网站上,未处理的重复提交可能导致订单被重复处理,从而引发多次扣款或多余的订单处理。
- 逻辑错误或资源滥用:在需要注意事务处理的系统中,重复的动作可能触发逻辑错误,例如库存管理、账户余额更新等领域。
- 负面影响用户信任度:用户可能因为看到重复扣费或订单错误而失去对平台的信任,这对业务的长期发展极为不利。
为何选择 more-request
?
在过去,我们的项目中出现了多次因为未能妥善处理按钮的重复点击而导致的重复提交问题。这不仅对后端系统造成了不必要的负担,而且在某些情况下还可能导致错误的数据处理或资金损失。为了避免每个页面或组件都需要单独实现防重处理,我们决定在更底层------即网络请求层面------来统一处理这些问题。
more-request
的开发初衷是减少团队在检查和修复每个表单或提交按钮上的重复性工作。通过在 Axios 请求层面添加智能的节流机制,more-request
允许开发人员继续使用他们习惯的方式编写代码,而不必担心额外的性能优化和错误防护措施。
more-request
的核心功能
- 统一管理请求 :
more-request
提供一种统一的方式来管理所有通过 Axios 发出的请求,确保整个应用的请求逻辑一致。无需修改现有的业务逻辑或检查每个单独的按钮,直接在网络请求级别实现控制。 - 自动节流与防重:不仅限制请求的频率,还通过智能识别和阻止短时间内的重复请求,保护后端服务不被过度请求。
- 完全兼容现有 Axios 实例 :开发者无需修改现有的请求代码,只需简单配置即可享受
more-request
带来的好处。 - 易于集成:通过简单的包装现有的 Axios 实例,不影响已有项目架构和代码实现。
- 高度自定义:开发者可以根据具体需求调整节流策略,确保应用的响应性与数据完整性。
与传统前端节流的对比
在传统的前端开发中,节流通常是在事件触发端(如按钮点击)实现,通过延迟执行或限定时间窗口来控制事件的处理频率。例如,使用 lodash
的 throttle
或 debounce
函数直接在事件处理函数中调用。这种方法虽简单,但存在以下局限:
- 分散实现:需要在每个需要节流的事件中单独实现,容易造成实现方式不一致。
- 复杂度高:在项目变得复杂时,管理各个地方的节流逻辑变得困难。
- 无法处理所有场景:例如,对于需要根据服务器响应来防止重复提交的场景,传统的节流技术无能为力。
相比之下,more-request
在网络请求层面实现节流和防重,提供了更为全面和一致的解决方案。
more-request
版本升级背景
自 2023 年 9 月发布以来,more-request
已经成为许多项目优化网络请求的首选工具。初版的设计主要是通过返回错误码 499 来阻止短时间内的重复请求,有效地减少了服务器负担。然而,在实际应用中,我们发现这种策略对于某些特定场景带来了挑战,尤其是在早期已经平稳运行的项目中,一些编码不规范的实践可能导致页面在更新后出现显示异常。为此,我们进行了策略的优化调整。
在项目的早期版本中,more-request
采用的是一种较为严格的节流策略:在检测到短时间内的重复请求时,不进行处理,直接返回错误码 499。这种做法虽然能够有效减轻服务器的压力,但在一些复杂的应用场景中,如单页面应用(SPA)中不同组件同时发起相同的请求,会导致用户界面错误或数据加载异常。
more-request
功能优化
为了解决这一问题,我们对 more-request
进行了功能上的优化。在最新的版本中,我们引入了一种更为智能的处理机制:当遇到短期内的完全重复请求时,我们不再简单地阻止请求并返回错误码,而是采用首个请求的结果作为后续重复请求的响应。这样,即使在不同组件或页面部分发起了相同的请求,用户也不会感受到任何不便,同时依然能够保证应用的性能和服务器的负载。
more-request
核心功能解析
在 more-request
的设计中,我们实现了一套复杂的请求管理逻辑,以确保高效和一致的请求处理。以下是该包中几个核心方法的详细解析:
请求唯一标识
所有进入 more-request
的请求首先通过 getRequestKey
方法处理,该方法根据请求的方法类型、URL、参数和数据内容生成一个唯一的键。这确保了即使是参数相似的请求也能被正确识别和管理。
javascript
const getRequestKey = ({ method, url, params, data }) =>
[method, url, formatString(params), formatString(data)].join('&');
请求记录和管理
我们使用一个 Map
数据结构来存储所有进行中的请求。每个请求都会被标记为 inProgress
和 waiting
状态,并关联一个 Promise 对象,这允许我们在后续操作中引用这些请求。
javascript
const getInitRequestRecord = (requestPromise) => ({
inProgress: true,
waiting: true,
requestPromise
});
延迟删除请求记录
delayRemovePendingRequest
函数用于处理请求完成后的状态更新和清理工作。如果请求已完成且不再等待状态,则会从记录中移除;如果请求仍在进行中,则标记为非等待状态。
javascript
const delayRemovePendingRequest = (config) => {
const requestKey = getRequestKey(config);
const requestRecord = requestsMap.get(requestKey);
setTimeout(() => {
if (!requestRecord.inProgress) {
requestsMap.delete(requestKey);
} else {
Object.assign(requestRecord, { waiting: false });
}
}, config.repeatCacheTime || DEFAULT_REPEAT_CACHE_TIME);
};
请求拦截和节流处理
在 setPendingRequest
函数中,我们检查传入请求是否已存在于 requestsMap
中。如果存在,并且请求还在进行中,则返回存储的 Promise 对象,实现请求的节流。如果不存在,则创建一个新的请求记录,并设置延时以管理请求的生命周期。
javascript
export const setPendingRequest = (config) => {
const requestKey = getRequestKey(config);
const requestRecord = requestsMap.get(requestKey);
if (requestRecord) {
return requestRecord.requestPromise; // 返回已存在的 Promise
}
const requestPromise = () => axiosInMap(config);
requestsMap.set(requestKey, getInitRequestRecord(requestPromise));
delayRemovePendingRequest(config);
return false; // 表示新的请求可以继续执行
};
大数字处理
在处理服务器响应数据时,由于 JavaScript 对大整数的处理存在限制(即超过 Number 类型的安全整数范围),more-request
使用 JSONBig
包来确保大数字的精确解析:
javascript
const JSONBigToString = JSONBig({ storeAsString: true });
axios.defaults.transformResponse = (data) => {
try {
return JSONBigToString.parse(data);
} catch {
// 如果解析失败,返回原始数据
return data;
}
};
通过这种方式,所有通过 more-request
发送的响应都将正确处理大数字,确保数据的准确性和完整性,这对于金融、科学计算等需要高精度数字处理的应用尤为重要。
重复请求拦截
more-request
在请求发送前通过 Axios 的请求拦截器进行检查,防止短时间内的重复请求导致不必要的服务器负担或潜在的数据错误:
javascript
axios.interceptors.request.use((config) => {
if (config.preventRepeat && SUPPORT_METHOD.has(config.method.toLowerCase())) {
const requestPromise = setPendingRequest(config);
if (requestPromise) {
// 返回自定义的错误和原有的 Promise
return Promise.reject({
code: 'ERR_REPEAT_REQUEST',
config,
message: 'Request is repeated',
name: 'EuiAxiosError',
useExistingPromise: true,
requestPromise,
response: {
status: 499,
statusText: 'Repeat Request',
},
});
}
}
return config;
});
这种机制不仅减少了服务器的处理压力,还避免了由于前端重复操作可能导致的错误,如重复提交表单。
响应和错误处理
在响应拦截器中,more-request
清理已完成的请求记录,并处理因重复请求产生的错误:
javascript
axios.interceptors.response.use(
(response) => {
if (response.config.preventRepeat) {
finishPendingRequest(response.config);
}
return response;
},
(error) => {
if (error.useExistingPromise) {
return error.requestPromise;
}
if (error.config.preventRepeat) {
finishPendingRequest(error.config);
}
return Promise.reject(error);
}
);
快速引入开发指南
bash
npm install more-request@latest
直接引入,默认实现防抖节流
js
import 'more-request'
引入实例
js
import axios from 'more-request/instance';
扩展axios
的config
参数
参数 | 默认值 | 说明 |
---|---|---|
preventRepeat |
true |
打开/关闭防重复提交功能 |
repeatCacheTime |
500 | 防重提交缓存请求时间 |
现在,所有重复的请求都将智能处理,无需担心过往的问题。
写在最后的话
通过使用 more-request
,团队可以有效减轻开发和测试的负担,提高项目的维护效率。这个工具的推出标志着我们对优化开发流程和提升应用稳定性的承诺。无论你是在处理大量表单数据还是需要管理频繁的用户交互,more-request
都将是你最可靠的助手。
现在就将 more-request
集成到你的项目中,体验前所未有的便捷和高效。more-request
打开了新的可能性,使开发者可以更加专注于创造出色的用户体验而不是处理后端压力。试试看,让你的应用在任何情况下都保持最佳性能!
通过持续的优化和改进,more-request
旨在为开发者提供一个强大且易于使用的工具,帮助他们优化应用性能,减少不必要的服务器压力,同时提升用户体验。我们鼓励所有开发者尝试这一工具,以提高其应用的性能和用户体验。欢迎反馈和贡献,共同推动 more-request
的发展。