如何将Ember Data请求限流至每秒X次?
实现Ember Data请求限流至每秒X次的可行方案
我之前在几个Ember项目里处理过和REST API的速率限制兼容的需求,下面是我验证过的几种靠谱实现方式,核心思路是通过拦截Ember Data的请求流程,用队列或节流机制控制请求发送频率。
方案一:自定义适配器 + 请求队列服务(推荐)
这个方案能严格控制每秒发送X次请求,还能处理请求排队、错误重试(可选)和请求去重,非常适合需要精确速率控制的场景。
1. 创建请求限流服务
首先生成一个Ember Service来管理请求队列:
// app/services/request-throttle.js import Service from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { config } from 'ember-get-config'; export default class RequestThrottleService extends Service { @tracked requestQueue = []; @tracked isProcessing = false; @tracked pendingRequests = new Map(); // 用于去重重复请求 // 从项目配置读取每秒允许的请求数,默认2 requestsPerSecond = config.requestThrottle?.requestsPerSecond || 2; // 计算每次请求的间隔时间 get requestInterval() { return 1000 / this.requestsPerSecond; } /** * 加入请求到队列 * @param {Function} requestFn - 包装原ajax请求的函数 * @param {String} key - 请求唯一标识,用于去重 * @param {Number} priority - 请求优先级,数值越高越先执行 * @returns {Promise} 请求结果的Promise */ enqueueRequest(requestFn, key, priority = 0) { // 去重:如果已有相同请求在队列或处理中,直接返回已有Promise if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key); } const promise = new Promise((resolve, reject) => { this.requestQueue.push({ requestFn, resolve, reject, priority }); // 按优先级排序队列,高优先级请求靠前 this.requestQueue.sort((a, b) => b.priority - a.priority); this.processQueue(); }).finally(() => { this.pendingRequests.delete(key); }); this.pendingRequests.set(key, promise); return promise; } /** * 处理队列中的请求 */ async processQueue() { if (this.isProcessing || this.requestQueue.length === 0) return; this.isProcessing = true; while (this.requestQueue.length > 0) { const { requestFn, resolve, reject } = this.requestQueue.shift(); try { // 执行原请求 const result = await requestFn(); resolve(result); } catch (error) { // 可以在这里添加重试逻辑,比如最多重试3次 reject(error); } // 等待指定间隔后再处理下一个请求 await new Promise(resolveTimer => setTimeout(resolveTimer, this.requestInterval)); } this.isProcessing = false; } }
2. 自定义REST适配器拦截请求
修改你的应用适配器,将所有Ember Data的ajax请求交给限流服务处理:
// app/adapters/application.js import RESTAdapter from '@ember-data/adapter/rest'; import { inject as service } from '@ember/service'; export default class ApplicationAdapter extends RESTAdapter { @service requestThrottle; /** * 重写ajax方法,加入限流逻辑 */ async ajax(url, method, options) { // 生成请求唯一标识,用于去重 const requestKey = `${method}:${url}:${JSON.stringify(options?.data || {})}`; // 读取请求优先级(可通过adapterOptions传递) const priority = options?.adapterOptions?.priority || 0; return this.requestThrottle.enqueueRequest(() => { // 调用Ember Data原生的ajax方法 return super.ajax(url, method, options); }, requestKey, priority); } }
3. 配置速率参数
在config/environment.js里添加限流配置(可选):
module.exports = function(environment) { let ENV = { // ...其他配置 requestThrottle: { requestsPerSecond: 3 // 每秒允许3次请求 } }; // ... return ENV; };
方案二:节流函数包装适配器请求(简易版)
如果你的需求不需要严格的队列控制,只是粗略限制请求频率,可以用节流函数包装适配器的ajax方法。注意:这种方式无法保证请求按顺序执行,也可能在异步场景下突破速率限制,适合对速率要求不高的场景。
// app/adapters/application.js import RESTAdapter from '@ember-data/adapter/rest'; // 自定义节流函数,处理异步请求 function throttleAsync(func, delay) { let lastCallTime = 0; let pendingPromise = null; return async function(...args) { const now = Date.now(); if (now - lastCallTime >= delay) { lastCallTime = now; pendingPromise = func.apply(this, args); return pendingPromise; } else { // 如果在节流间隔内,返回上一次的请求结果(或等待) return pendingPromise; } }; } export default class ApplicationAdapter extends RESTAdapter { constructor() { super(...arguments); // 包装ajax方法,限制每秒2次请求 this.ajax = throttleAsync(this.ajax.bind(this), 500); } }
关键注意事项
- 错误处理:如果某个请求失败,队列服务会自动继续处理下一个请求,不会阻塞整个队列;如果需要重试,可以在
processQueue的catch块中添加重试逻辑。 - 请求去重:队列服务中的
pendingRequestsMap可以避免重复的请求被多次发送,比如用户快速点击触发的相同查询。 - 优先级控制:通过
adapterOptions可以给特定请求设置高优先级,比如用户的即时操作请求优先于后台同步请求。
内容的提问来源于stack exchange,提问作者Drew
相关产品推荐
相关产品推荐

