资讯动态

API级防抖与节流在前端开发中的实践

发布时间:2026/10/27 3:30:15 来源:尧图企业网站定制
1. 为什么我们需要API级的防抖与节流在前端开发中表单重复提交是一个令人头疼的常见问题。想象这样一个场景用户在电商网站点击提交订单按钮时由于网络延迟页面没有立即响应用户可能会连续多次点击按钮。这会导致服务器收到多个相同的请求可能造成重复下单、重复扣款等严重问题。传统的防抖(debounce)和节流(throttle)实现通常作用于DOM事件层面比如按钮的点击事件。但这种方法存在明显缺陷无法防止通过不同入口触发的重复提交比如快捷键、浏览器刷新等无法在页面刷新后保持防抖状态难以与后端API调用深度集成无法实现跨组件、跨路由的状态共享API级的防抖与节流则直接在请求层面进行控制无论请求从何处发起都能确保同一API在一定时间内不会被重复调用。这种方案具有以下优势全局性覆盖所有可能的请求入口持久性即使页面刷新也能保持状态精确性可以针对不同API设置不同的防抖/节流策略可维护性集中管理所有API的调用频率控制逻辑2. 核心实现原理与技术选型2.1 基于Map的数据结构设计我们选择ES6的Map对象作为核心数据结构主要基于以下考虑const apiThrottleMap new Map();Map相比普通对象的优势键可以是任意类型包括函数、对象等而普通对象只能用字符串或Symbol作为键内置size属性便于统计提供forEach、entries等便捷的遍历方法性能更优特别是在频繁增删键值对的场景2.2 闭包与高阶函数的运用闭包(closure)在这里发挥关键作用它允许我们保持对Map的私有访问避免全局污染封装内部状态如计时器、最后一次调用时间等创建具有记忆功能的函数高阶函数(HOF)模式让我们可以装饰原始API函数添加防抖/节流逻辑而不修改原函数function withThrottle(originalFn, options) { return function(...args) { // 添加节流逻辑 return originalFn.apply(this, args); }; }2.3 防抖与节流的本质区别虽然常被一起讨论但防抖和节流解决的是不同问题特性防抖(debounce)节流(throttle)触发时机最后一次操作后延迟执行固定间隔执行一次适用场景搜索建议、窗口resize滚动事件、鼠标移动重复处理取消前一次未执行的调用保证周期性执行实现方式清除并重置定时器比较时间戳判断是否执行对于表单提交通常更适合使用防抖因为我们需要确保用户最后一次操作被正确处理而不是周期性执行。3. 完整实现方案与代码解析3.1 基础架构设计我们先构建一个API调用管理器class APIManager { constructor() { this.apiMap new Map(); // 存储API调用状态 this.defaultOptions { wait: 1000, // 默认等待时间 leading: false, // 是否立即执行第一次 trailing: true // 是否在延迟后执行最后一次 }; } // 注册API registerAPI(apiKey, originalFn, options {}) { const mergedOptions { ...this.defaultOptions, ...options }; const throttledFn this.createThrottledFn(originalFn, mergedOptions); this.apiMap.set(apiKey, { originalFn, throttledFn, options: mergedOptions, lastCalled: 0, timer: null }); return throttledFn; } // 创建节流函数 createThrottledFn(originalFn, options) { return function(...args) { const now Date.now(); const context this; const { wait, leading, trailing } options; // 节流核心逻辑 if (leading now - this.lastCalled wait) { this.lastCalled now; return originalFn.apply(context, args); } // 防抖核心逻辑 clearTimeout(this.timer); this.timer setTimeout(() { this.lastCalled now; originalFn.apply(context, args); }, wait); }.bind(this.apiMap.get(apiKey)); } }3.2 实际使用示例假设我们有一个提交订单的API// 原始API函数 async function submitOrder(orderData) { const response await fetch(/api/orders, { method: POST, body: JSON.stringify(orderData), headers: { Content-Type: application/json } }); return response.json(); } // 创建API管理器实例 const apiManager new APIManager(); // 注册防抖版本的提交函数 const debouncedSubmit apiManager.registerAPI( submitOrder, submitOrder, { wait: 2000, leading: false, trailing: true } ); // 在组件中使用 orderForm.addEventListener(submit, async (e) { e.preventDefault(); const orderData getFormData(); try { const result await debouncedSubmit(orderData); showSuccessMessage(); } catch (error) { showErrorMessage(error); } });3.3 高级功能扩展3.3.1 动态调整参数我们可以扩展APIManager支持运行时调整防抖/节流参数class APIManager { // ...其他代码 updateAPI(apiKey, newOptions) { if (!this.apiMap.has(apiKey)) return; const apiRecord this.apiMap.get(apiKey); apiRecord.options { ...apiRecord.options, ...newOptions }; // 重新创建节流函数以应用新参数 apiRecord.throttledFn this.createThrottledFn( apiRecord.originalFn, apiRecord.options ); } }3.3.2 请求取消功能添加取消未完成请求的能力class APIManager { // ...其他代码 cancelAPI(apiKey) { const apiRecord this.apiMap.get(apiKey); if (apiRecord apiRecord.timer) { clearTimeout(apiRecord.timer); apiRecord.timer null; return true; } return false; } // 在createThrottledFn中保存取消token createThrottledFn(originalFn, options) { return function(...args) { const cancelToken { cancelled: false }; // ...原有逻辑 // 在定时器中检查取消状态 this.timer setTimeout(() { if (cancelToken.cancelled) return; // ...执行原函数 }, wait); return { promise: /* 返回的Promise */, cancel: () { cancelToken.cancelled true; } }; }.bind(this.apiMap.get(apiKey)); } }4. 实际应用中的优化与陷阱4.1 性能优化技巧Map大小控制长时间运行的SPA应用可能导致Map无限增长需要定期清理class APIManager { constructor(maxSize 50) { this.maxSize maxSize; // ...其他初始化 } registerAPI(apiKey, originalFn, options) { // 超过最大大小时清理最久未使用的 if (this.apiMap.size this.maxSize) { let oldestKey, oldestTime Infinity; this.apiMap.forEach((record, key) { if (record.lastCalled oldestTime) { oldestTime record.lastCalled; oldestKey key; } }); this.apiMap.delete(oldestKey); } // ...原有注册逻辑 } }内存泄漏预防确保在组件卸载时清理定时器// React示例 useEffect(() { const debouncedSubmit apiManager.registerAPI(submitOrder, submitOrder); return () { apiManager.cancelAPI(submitOrder); }; }, []);4.2 常见问题与解决方案问题1防抖导致用户反馈延迟用户点击后没有任何反应直到防抖时间结束才显示加载状态体验不佳。解决方案分离UI反馈与API调用立即显示加载状态同时防抖API调用orderForm.addEventListener(submit, async (e) { e.preventDefault(); showLoading(); // 立即显示加载状态 try { await debouncedSubmit(getFormData()); showSuccess(); } catch (error) { showError(error); } finally { hideLoading(); // 在Promise解决后隐藏 } });问题2多标签页状态不同步用户在一个标签页提交后另一个标签页仍可提交相同请求。解决方案使用localStorage或SharedWorker同步状态后端添加幂等性校验// 跨标签页同步的增强版APIManager class SharedAPIManager extends APIManager { constructor() { super(); window.addEventListener(storage, (e) { if (e.key apiThrottleState) { this.syncState(JSON.parse(e.newValue)); } }); } updateState(apiKey, state) { super.updateState(apiKey, state); this.saveStateToStorage(); } saveStateToStorage() { const state {}; this.apiMap.forEach((record, key) { state[key] { lastCalled: record.lastCalled, options: record.options }; }); localStorage.setItem(apiThrottleState, JSON.stringify(state)); } syncState(state) { Object.entries(state).forEach(([key, value]) { if (this.apiMap.has(key)) { this.apiMap.get(key).lastCalled value.lastCalled; } }); } }4.3 测试策略确保防抖/节流逻辑正确工作需要专门的测试方案describe(APIManager, () { let apiManager; let mockApi; beforeEach(() { apiManager new APIManager(); mockApi jest.fn().mockResolvedValue(success); jest.useFakeTimers(); }); test(should debounce API calls, () { const debouncedFn apiManager.registerAPI(test, mockApi, { wait: 100 }); // 快速连续调用3次 debouncedFn(); debouncedFn(); debouncedFn(); // 此时不应立即执行 expect(mockApi).not.toHaveBeenCalled(); // 快进时间 jest.advanceTimersByTime(100); // 应该只执行最后一次 expect(mockApi).toHaveBeenCalledTimes(1); }); test(should throttle leading edge, () { const throttledFn apiManager.registerAPI(test, mockApi, { wait: 100, leading: true, trailing: false }); // 第一次调用应该立即执行 throttledFn(); expect(mockApi).toHaveBeenCalledTimes(1); // 快速连续调用 throttledFn(); throttledFn(); // 在等待期内不应再执行 jest.advanceTimersByTime(50); expect(mockApi).toHaveBeenCalledTimes(1); // 等待期过后可以再次执行 jest.advanceTimersByTime(50); throttledFn(); expect(mockApi).toHaveBeenCalledTimes(2); }); });5. 与现有技术栈的集成5.1 在React中的最佳实践对于React项目我们可以创建自定义Hookimport { useRef, useEffect, useCallback } from react; export function useDebouncedAPI(apiKey, originalFn, options) { const apiManagerRef useRef(); if (!apiManagerRef.current) { apiManagerRef.current new APIManager(); } // 注册API仅在初始化和参数变化时执行 const registeredFn useCallback( apiManagerRef.current.registerAPI(apiKey, originalFn, options), [apiKey, originalFn, options] ); // 组件卸载时清理 useEffect(() { return () { apiManagerRef.current.cancelAPI(apiKey); }; }, [apiKey]); return registeredFn; } // 使用示例 function OrderForm() { const debouncedSubmit useDebouncedAPI( submitOrder, submitOrder, { wait: 1000 } ); const handleSubmit async (orderData) { try { await debouncedSubmit(orderData); // 显示成功 } catch (error) { // 处理错误 } }; // ...渲染表单 }5.2 在Vue中的实现方式对于Vue项目我们可以创建插件// apiThrottle.js const ApiThrottlePlugin { install(app, options {}) { const apiManager new APIManager(options); app.config.globalProperties.$api { register: apiManager.registerAPI.bind(apiManager), cancel: apiManager.cancelAPI.bind(apiManager) }; // 提供inject能力 app.provide(apiThrottle, apiManager); } }; // main.js import { createApp } from vue; import App from ./App.vue; import { ApiThrottlePlugin } from ./plugins/apiThrottle; const app createApp(App); app.use(ApiThrottlePlugin, { defaultWait: 1000 }); app.mount(#app); // 组件中使用 export default { methods: { async submitForm() { try { const result await this.$api.register( submitForm, this.actualSubmit, { wait: 1500 } )(); // 处理结果 } catch (error) { // 处理错误 } }, actualSubmit(formData) { return axios.post(/api/submit, formData); } } }5.3 与状态管理库集成对于Redux或Vuex我们可以创建中间件/插件// Redux中间件 const apiThrottleMiddleware (apiManager) (store) (next) (action) { if (!action.meta || !action.meta.throttle) { return next(action); } const { type, meta: { throttle } } action; const originalAction { ...action, meta: { ...action.meta, throttle: undefined } }; const throttledDispatch apiManager.registerAPI( redux_${type}, () next(originalAction), throttle ); return throttledDispatch(); }; // 使用示例 const store createStore( rootReducer, applyMiddleware(apiThrottleMiddleware(new APIManager())) ); // 在action creator中 function submitOrder(data) { return { type: SUBMIT_ORDER, payload: data, meta: { throttle: { wait: 1000, leading: false, trailing: true } } }; }6. 高级场景与边缘情况处理6.1 差异化防抖策略不同API可能需要不同的防抖策略// 在APIManager中扩展 class APIManager { // ...其他代码 registerAPI(apiKey, originalFn, options) { // 根据API特征自动选择策略 const autoOptions this.autoDetectStrategy(apiKey, originalFn, options); const mergedOptions { ...this.defaultOptions, ...autoOptions }; // ...原有注册逻辑 } autoDetectStrategy(apiKey, originalFn, options) { // 根据API路径或方法自动判断 if (apiKey.includes(search)) { return { wait: 300, leading: false, trailing: true }; } if (apiKey.includes(submit)) { return { wait: 1000, leading: false, trailing: true }; } if (originalFn.length 1) { // 参数较多的可能是复杂操作 return { wait: 500, leading: true, trailing: false }; } return {}; } }6.2 错误重试机制结合防抖与错误重试class APIManager { // ...其他代码 createThrottledFn(originalFn, options) { return async function(...args) { const maxRetries options.retries || 0; let lastError; for (let i 0; i maxRetries; i) { try { const result await new Promise((resolve, reject) { // ...原有防抖逻辑但调用originalFn时处理Promise const execute () { originalFn.apply(this, args) .then(resolve) .catch(reject); }; // 防抖/节流逻辑包装execute }); return result; } catch (error) { lastError error; if (i maxRetries) { await new Promise(r setTimeout(r, options.retryDelay || 1000)); } } } throw lastError; }.bind(this.apiMap.get(apiKey)); } }6.3 并发请求控制限制同时进行的相同API调用数量class APIManager { constructor() { this.pendingCounts new Map(); // 跟踪进行中的请求数 } createThrottledFn(originalFn, options) { return async function(...args) { const apiKey this.apiKey; const maxConcurrent options.maxConcurrent || 1; if ((this.pendingCounts.get(apiKey) || 0) maxConcurrent) { throw new Error(Too many concurrent requests for ${apiKey}); } this.pendingCounts.set( apiKey, (this.pendingCounts.get(apiKey) || 0) 1 ); try { const result await new Promise((resolve, reject) { // ...原有防抖逻辑 }); return result; } finally { this.pendingCounts.set( apiKey, (this.pendingCounts.get(apiKey) || 0) - 1 ); } }.bind({ ...this.apiMap.get(apiKey), pendingCounts: this.pendingCounts, apiKey }); } }7. 性能监控与调优7.1 添加性能指标收集class APIManager { constructor() { this.metrics { totalCalls: 0, throttledCalls: 0, savedCalls: 0, byApi: new Map() }; } createThrottledFn(originalFn, options) { return function(...args) { this.metrics.totalCalls; const apiMetrics this.getApiMetrics(apiKey); apiMetrics.totalCalls; // ...原有防抖逻辑中增加指标收集 if (/* 调用被节流 */) { this.metrics.throttledCalls; apiMetrics.throttledCalls; } if (/* 调用被防抖取消 */) { this.metrics.savedCalls; apiMetrics.savedCalls; } // ...执行原函数 }.bind(this); } getApiMetrics(apiKey) { if (!this.metrics.byApi.has(apiKey)) { this.metrics.byApi.set(apiKey, { totalCalls: 0, throttledCalls: 0, savedCalls: 0, lastCalled: 0 }); } return this.metrics.byApi.get(apiKey); } getMetrics() { return { ...this.metrics, byApi: Object.fromEntries(this.metrics.byApi) }; } }7.2 可视化监控面板基于收集的指标可以创建简单的监控面板class APIMonitor { constructor(apiManager) { this.apiManager apiManager; this.initUI(); setInterval(() this.updateStats(), 1000); } initUI() { this.statsPanel document.createElement(div); this.statsPanel.style.position fixed; this.statsPanel.style.bottom 10px; this.statsPanel.style.right 10px; this.statsPanel.style.backgroundColor rgba(0,0,0,0.7); this.statsPanel.style.color white; this.statsPanel.style.padding 10px; this.statsPanel.style.borderRadius 5px; this.statsPanel.style.zIndex 9999; document.body.appendChild(this.statsPanel); } updateStats() { const metrics this.apiManager.getMetrics(); let html h3API调用统计/h3 p总调用: ${metrics.totalCalls}/p p节流调用: ${metrics.throttledCalls}/p p节省调用: ${metrics.savedCalls}/p h4按API统计/h4; Object.entries(metrics.byApi).forEach(([api, stats]) { html pstrong${api}/strong: ${stats.totalCalls}次 (${stats.throttledCalls}节流, ${stats.savedCalls}节省)/p; }); this.statsPanel.innerHTML html; } } // 使用 const apiManager new APIManager(); new APIMonitor(apiManager);7.3 动态调整策略基于实时性能数据自动调整防抖/节流参数class AutoTuningAPIManager extends APIManager { constructor() { super(); this.performanceThresholds { highLoad: 100, // 每分钟超过100次调用视为高负载 lowLoad: 10 // 每分钟低于10次视为低负载 }; setInterval(() this.autoTune(), 60000); // 每分钟调整一次 } autoTune() { const metrics this.getMetrics(); this.apiMap.forEach((record, apiKey) { const apiMetrics metrics.byApi[apiKey] || {}; const callsPerMinute apiMetrics.totalCalls / (metrics.collectionPeriod / 60000); if (callsPerMinute this.performanceThresholds.highLoad) { // 高负载时增加等待时间 this.updateAPI(apiKey, { wait: Math.min(record.options.wait * 1.5, 5000) }); } else if (callsPerMinute this.performanceThresholds.lowLoad) { // 低负载时减少等待时间 this.updateAPI(apiKey, { wait: Math.max(record.options.wait * 0.8, 200) }); } }); this.resetMetrics(); } }8. 安全考量与最佳实践8.1 防抖与安全令牌处理需要安全令牌的API时需特别注意class SecureAPIManager extends APIManager { createThrottledFn(originalFn, options) { return async function(...args) { // 检查令牌是否即将过期 if (this.isTokenExpiringSoon()) { await this.refreshToken(); } // 确保每次调用使用最新令牌 const newArgs this.injectFreshToken(args); // ...原有防抖逻辑但使用newArgs }.bind(this); } isTokenExpiringSoon() { // 实现令牌过期检查逻辑 } refreshToken() { // 实现令牌刷新逻辑 } injectFreshToken(args) { // 实现令牌注入逻辑 } }8.2 敏感操作的特殊处理对于支付、密码修改等敏感操作禁用防抖/节流确保每次操作都立即执行添加二次确认记录详细操作日志class SensitiveAPIManager extends APIManager { registerAPI(apiKey, originalFn, options) { if (this.isSensitiveAPI(apiKey)) { return async (...args) { const confirmed await this.showConfirmation(apiKey); if (!confirmed) throw new Error(User cancelled); return originalFn(...args); }; } return super.registerAPI(apiKey, originalFn, options); } isSensitiveAPI(apiKey) { return [payment, changePassword, deleteAccount] .some(keyword apiKey.includes(keyword)); } async showConfirmation(apiKey) { // 实现二次确认UI } }8.3 服务端协同防护前端防抖不能替代服务端防护必须实现幂等性设计相同请求多次执行结果一致请求去重基于唯一请求ID识别重复请求速率限制限制单位时间内的请求次数// 前端生成唯一请求ID function generateRequestId(apiKey, args) { const argString JSON.stringify(args); const hash btoa(encodeURIComponent(argString)).substr(0, 16); return ${apiKey}_${Date.now()}_${hash}; } // 修改APIManager发送请求ID class APIManager { createThrottledFn(originalFn, options) { return function(...args) { const requestId generateRequestId(apiKey, args); // 修改原始函数添加请求ID const enhancedFn () originalFn.call(this, { ...args, headers: { ...args.headers, X-Request-ID: requestId } }); // ...原有防抖逻辑但调用enhancedFn }.bind(this.apiMap.get(apiKey)); } }9. 替代方案与比较9.1 基于Web Worker的实现将防抖逻辑移到Web Worker中可以避免主线程阻塞保持状态即使页面被隐藏实现更精确的定时控制// worker.js const apiStates new Map(); self.onmessage function(e) { const { type, apiKey, timestamp, wait } e.data; if (type check) { const lastCalled apiStates.get(apiKey) || 0; const allowed timestamp - lastCalled wait; if (allowed) { apiStates.set(apiKey, timestamp); } self.postMessage({ type: response, apiKey, allowed, timestamp }); } }; // 主线程中的APIManager修改 class WorkerAPIManager { constructor() { this.worker new Worker(worker.js); this.callbacks new Map(); this.worker.onmessage (e) { const { apiKey, allowed, timestamp } e.data; const callback this.callbacks.get(${apiKey}_${timestamp}); if (callback) { callback(allowed); this.callbacks.delete(${apiKey}_${timestamp}); } }; } registerAPI(apiKey, originalFn, options) { return async function(...args) { const timestamp Date.now(); return new Promise((resolve, reject) { this.callbacks.set(${apiKey}_${timestamp}, (allowed) { if (allowed) { originalFn(...args).then(resolve).catch(reject); } else { reject(new Error(Request throttled)); } }); this.worker.postMessage({ type: check, apiKey, timestamp, wait: options.wait }); }); }.bind(this); } }9.2 基于Service Worker的离线处理Service Worker可以在网络恢复后处理积压的请求实现更复杂的队列管理提供离线缓存能力// service-worker.js const API_QUEUE api-queue; const THROTTLE_MAP throttle-map; self.addEventListener(fetch, (event) { if (event.request.url.includes(/api/)) { event.respondWith( handleAPIRequest(event.request) ); } }); async function handleAPIRequest(request) { const cache await caches.open(THROTTLE_MAP); const url request.url; const now Date.now(); const cachedResponse await cache.match(url); const lastCalled cachedResponse ? parseInt(await cachedResponse.text()) : 0; const wait 1000; // 可从请求中读取配置 if (now - lastCalled wait) { // 节流期间将请求加入队列 const queueCache await caches.open(API_QUEUE); await queueCache.put(url, request.clone()); return new Response(null, { status: 202 }); // Accepted } // 更新最后调用时间 await cache.put(url, new Response(now.toString())); // 执行实际请求 return fetch(request); } // 定期处理积压队列 self.addEventListener(periodicsync, (event) { if (event.tag process-api-queue) { event.waitUntil(processQueue()); } }); async function processQueue() { const queueCache await caches.open(API_QUEUE); const throttleCache await caches.open(THROTTLE_MAP); const requests await queueCache.keys(); const now Date.now(); for (const request of requests) { const url request.url; const lastCalled await throttleCache.match(url) .then(r r ? parseInt(r.text()) : 0); if (now - lastCalled 1000) { await throttleCache.put(url, new Response(now.toString())); await fetch(request); await queueCache.delete(url); } } }9.3 各方案对比方案优点缺点适用场景纯前端Map闭包实现简单无额外依赖页面刷新后状态丢失简单应用短时操作Web Worker精确计时不阻塞UI实现复杂度较高需要精确控制的复杂应用Service Worker支持离线队列管理需要HTTPS学习曲线陡峭PWA离线优先应用服务端协同最可靠防作弊需要前后端协作关键业务安全敏感操作10. 未来演进方向10.1 基于AI的动态调整未来可以考虑引入机器学习模型基于历史调用模式动态预测最佳防抖参数class AITunedAPIManager extends APIManager { constructor() { super(); this.usagePatterns new Map(); this.loadModel(); } async loadModel() { // 加载预训练的TensorFlow.js模型 this.model await tf.loadLayersModel(path/to/model.json); } recordUsage(apiKey, timestamp) { if (!this.usagePatterns.has(apiKey)) { this.usagePatterns.set(apiKey, []); } this.usagePatterns.get(apiKey).push(timestamp); } predictOptimalWait(apiKey) { const timestamps this.usagePatterns.get(apiKey) || []; if (timestamps.length 10) return this.defaultOptions.wait; // 使用模型预测最佳等待时间 const input this.preprocessTimestamps(timestamps); const prediction this.model.predict(input); return prediction * 1000; // 转换为毫秒 } preprocessTimestamps(timestamps) { // 实现时间序列预处理 } registerAPI(apiKey, originalFn, options) { const throttledFn super.registerAPI(apiKey, originalFn, options); return async function(...args) { this.recordUsage(apiKey, Date.now()); const optimalWait this.predictOptimalWait(apiKey); this.updateAPI(apiKey, { wait: optimalWait }); return throttledFn(...args); }.bind(this); } }10.2 分布式状态同步对于微前端架构或多标签应用可以实现跨窗口的状态同步class SharedStateAPIManager extends APIManager { constructor() { super(); this.broadcastChannel new BroadcastChannel(api-throttle); this.broadcastChannel.addEventListener(message, (event) { const { type, apiKey, timestamp } event.data; if (type apiCalled) { const record this.apiMap.get(apiKey); if (record) { record.lastCalled Math.max(record.lastCalled, timestamp); } } }); } createThrottledFn(originalFn, options) { return function(...args) { // ...原有防抖逻辑 // 在执行实际调用前广播 this.broadcastChannel.postMessage({ type: apiCalled, apiKey: this.apiKey, timestamp: Date.now() }); return originalFn.apply(this, args); }.bind({ ...this.apiMap.get(apiKey), broadcastChannel: this.broadcastChannel, apiKey }); } }10.3 与GraphQL的深度集成对于使用GraphQL的应用可以在查询层面实现防抖class GraphQLThrottler { constructor(schema) { this.schema schema; this.operationMap new Map(); } throttleOperation(operationName, options) { const originalResolve this.schema.getResolver(operationName); this.schema.setResolver(operationName, async (source, args, context, info) { const now Date.now(); const lastCalled this.operationMap.get(operationName) || 0; if (now - lastCalled options.wait) { throw new Error(Operation ${operationName} throttled); } this.operationMap.set(operationName, now); return originalResolve(source,

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑